Compare commits
91 Commits
master
...
require_is
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3a4a3c23a | ||
|
|
86694397a6 | ||
|
|
b18c074088 | ||
|
|
f59a639ec4 | ||
|
|
5411e4c86a | ||
|
|
0425b87b78 | ||
|
|
1de966d637 | ||
|
|
ba2e4aecb7 | ||
|
|
7d2b1996d9 | ||
|
|
dcd52b0711 | ||
|
|
8074858620 | ||
|
|
d3d293299a | ||
|
|
9148acd332 | ||
|
|
9f9a1df85e | ||
|
|
985457e57b | ||
|
|
4004589cbf | ||
|
|
8f7c3756b3 | ||
|
|
6ea5da51e0 | ||
|
|
1ac05ae891 | ||
|
|
a441debdaa | ||
|
|
5dd898f56b | ||
|
|
a1fce6b398 | ||
|
|
635d9c3ec3 | ||
|
|
0f36d0c1ba | ||
|
|
619da0432a | ||
|
|
314838eb81 | ||
|
|
25a1933e01 | ||
|
|
0f500e8010 | ||
|
|
1afae838bb | ||
|
|
724be8848a | ||
|
|
06ea141ca9 | ||
|
|
23245c07b2 | ||
|
|
2d1d1cd534 | ||
|
|
5240e3cbf2 | ||
|
|
5fca4a14ab | ||
|
|
9be070414f | ||
|
|
761c268934 | ||
|
|
1fa110c9d7 | ||
|
|
9d1fccf53a | ||
|
|
b16c64a05b | ||
|
|
88932da859 | ||
|
|
8a69b2f1d9 | ||
|
|
57b5942fc3 | ||
|
|
0f0000298b | ||
|
|
c39fbe388c | ||
|
|
fc07f5f935 | ||
|
|
9c8cdd05da | ||
|
|
2c8609464c | ||
|
|
7a48c235a9 | ||
|
|
3f9387bd15 | ||
|
|
087cb4d1f4 | ||
|
|
bcbc3dd484 | ||
|
|
89737718c1 | ||
|
|
b0e389aad7 | ||
|
|
9f5451d16b | ||
|
|
5ada20cb74 | ||
|
|
ba256adcdb | ||
|
|
94adff7247 | ||
|
|
06d6231d6d | ||
|
|
61da18cc47 | ||
|
|
7ab421233e | ||
|
|
59f32c9d11 | ||
|
|
2b57c5f03c | ||
|
|
8f75af1e84 | ||
|
|
3c9b936168 | ||
|
|
93294fc989 | ||
|
|
58a07ddd79 | ||
|
|
933c1703b6 | ||
|
|
a44d739dd7 | ||
|
|
58374867c8 | ||
|
|
aa6ea3b513 | ||
|
|
a25ef72c4f | ||
|
|
396b6cce02 | ||
|
|
38fc7fcc48 | ||
|
|
0e225dcba2 | ||
|
|
4ff5719a65 | ||
|
|
798a61622c | ||
|
|
b20d01e032 | ||
|
|
990352e371 | ||
|
|
c5a5d6f9a1 | ||
|
|
d4850399c5 | ||
|
|
c4be440853 | ||
|
|
165c3e32b0 | ||
|
|
2660a2017b | ||
|
|
6a6544fd90 | ||
|
|
320cf92944 | ||
|
|
3078c2f3db | ||
|
|
c54f99e35b | ||
|
|
c81dbb2582 | ||
|
|
742f97e11a | ||
|
|
84c8dbc52a |
@@ -1,8 +1,8 @@
|
||||
# Configuring Azure Pipelines with Certbot
|
||||
|
||||
Let's begin. All pipelines are defined in `.azure-pipelines`. Currently there are two:
|
||||
* `.azure-pipelines/main.yml` is the main one, executed on PRs for master, and pushes to master,
|
||||
* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for master.
|
||||
* `.azure-pipelines/main.yml` is the main one, executed on PRs for main, and pushes to main,
|
||||
* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for main.
|
||||
|
||||
Several templates are defined in `.azure-pipelines/templates`. These YAML files aggregate common jobs configuration that can be reused in several pipelines.
|
||||
|
||||
@@ -64,7 +64,7 @@ Azure Pipeline needs RW on code, RO on metadata, RW on checks, commit statuses,
|
||||
RW access here is required to allow update of the pipelines YAML files from Azure DevOps interface, and to
|
||||
update the status of builds and PRs on GitHub side when Azure Pipelines are triggered.
|
||||
Note however that no admin access is defined here: this means that Azure Pipelines cannot do anything with
|
||||
protected branches, like master, and cannot modify the security context around this on GitHub.
|
||||
protected branches, like main, and cannot modify the security context around this on GitHub.
|
||||
Access can be defined for all or only selected repositories, which is nice.
|
||||
```
|
||||
|
||||
@@ -91,11 +91,11 @@ grant permissions from Azure Pipelines to GitHub in order to setup a GitHub OAut
|
||||
then are way too large (admin level on almost everything), while the classic approach does not add any more
|
||||
permissions, and works perfectly well.__
|
||||
|
||||
- Select GitHub in "Select your repository section", choose certbot/certbot in Repository, master in default branch.
|
||||
- Select GitHub in "Select your repository section", choose certbot/certbot in Repository, main in default branch.
|
||||
- Click on YAML option for "Select a template"
|
||||
- Choose a name for the pipeline (eg. test-pipeline), and browse to the actual pipeline YAML definition in the
|
||||
"YAML file path" input (eg. `.azure-pipelines/test-pipeline.yml`)
|
||||
- Click "Save & queue", choose the master branch to build the first pipeline, and click "Save and run" button.
|
||||
- Click "Save & queue", choose the main branch to build the first pipeline, and click "Save and run" button.
|
||||
|
||||
_Done. Pipeline is operational. Repeat to add more pipelines from existing YAML files in `.azure-pipelines`._
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# We run the test suite on commits to master so codecov gets coverage data
|
||||
# about the master branch and can use it to track coverage changes.
|
||||
# We run the test suite on commits to main so codecov gets coverage data
|
||||
# about the main branch and can use it to track coverage changes.
|
||||
trigger:
|
||||
- master
|
||||
- main
|
||||
pr:
|
||||
- master
|
||||
- main
|
||||
- '*.x'
|
||||
|
||||
variables:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Nightly pipeline running each day for master.
|
||||
# Nightly pipeline running each day for main.
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
@@ -6,7 +6,7 @@ schedules:
|
||||
displayName: Nightly build
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- main
|
||||
always: true
|
||||
|
||||
variables:
|
||||
|
||||
@@ -20,13 +20,11 @@ jobs:
|
||||
linux-isolated:
|
||||
TOXENV: 'isolated-acme,isolated-certbot,isolated-apache,isolated-cloudflare,isolated-digitalocean,isolated-dnsimple,isolated-dnsmadeeasy,isolated-gehirn,isolated-google,isolated-linode,isolated-luadns,isolated-nsone,isolated-ovh,isolated-rfc2136,isolated-route53,isolated-sakuracloud,isolated-nginx'
|
||||
linux-integration-certbot-oldest:
|
||||
PYTHON_VERSION: 3.8
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: integration-certbot-oldest
|
||||
linux-integration-nginx-oldest:
|
||||
PYTHON_VERSION: 3.8
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: integration-nginx-oldest
|
||||
# python 3.8 integration tests are not run here because they're run as
|
||||
# part of the standard test suite
|
||||
linux-py39-integration:
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: integration
|
||||
@@ -36,20 +34,19 @@ jobs:
|
||||
linux-py311-integration:
|
||||
PYTHON_VERSION: 3.11
|
||||
TOXENV: integration
|
||||
linux-py312-integration:
|
||||
PYTHON_VERSION: 3.12
|
||||
TOXENV: integration
|
||||
# python 3.12 integration tests are not run here because they're run as
|
||||
# part of the standard test suite
|
||||
nginx-compat:
|
||||
TOXENV: nginx_compat
|
||||
linux-integration-rfc2136:
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
PYTHON_VERSION: 3.8
|
||||
PYTHON_VERSION: 3.12
|
||||
TOXENV: integration-dns-rfc2136
|
||||
le-modification:
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
TOXENV: modification
|
||||
farmtest-apache2:
|
||||
PYTHON_VERSION: 3.8
|
||||
PYTHON_VERSION: 3.12
|
||||
TOXENV: test-farm-apache2
|
||||
pool:
|
||||
vmImage: $(IMAGE_NAME)
|
||||
|
||||
@@ -4,27 +4,23 @@ jobs:
|
||||
PYTHON_VERSION: 3.12
|
||||
strategy:
|
||||
matrix:
|
||||
macos-py38-cover:
|
||||
IMAGE_NAME: macOS-12
|
||||
PYTHON_VERSION: 3.8
|
||||
macos-cover:
|
||||
IMAGE_NAME: macOS-15
|
||||
TOXENV: cover
|
||||
# As of pip 23.1.0, builds started failing on macOS unless this flag was set.
|
||||
# See https://github.com/certbot/certbot/pull/9717#issuecomment-1610861794.
|
||||
PIP_USE_PEP517: "true"
|
||||
macos-cover:
|
||||
IMAGE_NAME: macOS-13
|
||||
TOXENV: cover
|
||||
# See explanation under macos-py38-cover.
|
||||
PIP_USE_PEP517: "true"
|
||||
linux-oldest:
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
PYTHON_VERSION: 3.8
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: oldest
|
||||
linux-py38:
|
||||
linux-py39:
|
||||
# linux unit tests with the oldest python we support
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: py38
|
||||
PYTHON_VERSION: 3.9
|
||||
TOXENV: py39
|
||||
linux-cover:
|
||||
# linux unit+cover tests with the newest python we support
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
TOXENV: cover
|
||||
linux-lint:
|
||||
@@ -35,7 +31,6 @@ jobs:
|
||||
TOXENV: mypy
|
||||
linux-integration:
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: integration
|
||||
apache-compat:
|
||||
IMAGE_NAME: ubuntu-22.04
|
||||
@@ -52,6 +47,6 @@ jobs:
|
||||
- template: ../steps/tox-steps.yml
|
||||
- job: test_sphinx_builds
|
||||
pool:
|
||||
vmImage: ubuntu-20.04
|
||||
vmImage: ubuntu-22.04
|
||||
steps:
|
||||
- template: ../steps/sphinx-steps.yml
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -1,6 +1,6 @@
|
||||
## Pull Request Checklist
|
||||
|
||||
- [ ] The Certbot team has recently expressed interest in reviewing a PR for this. If not, this PR may be closed due our limited resources and need to prioritize how we spend them.
|
||||
- [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `master` section of `certbot/CHANGELOG.md` to include a description of the change being made.
|
||||
- [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `main` section of `certbot/CHANGELOG.md` to include a description of the change being made.
|
||||
- [ ] Add or update any documentation as needed to support the changes in this PR.
|
||||
- [ ] Include your name in `AUTHORS.md` if you like.
|
||||
|
||||
4
.github/workflows/merged.yaml
vendored
4
.github/workflows/merged.yaml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
if_merged:
|
||||
# Forked repos can not access Mattermost secret.
|
||||
if: github.event.pull_request.merged == true && !github.event.pull_request.head.repo.fork
|
||||
if: github.event.pull_request.merged == true && !github.event.pull_request.head.repo.fork
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mattermost/action-mattermost-notify@master
|
||||
@@ -18,4 +18,4 @@ jobs:
|
||||
[${{ github.repository }}] |
|
||||
[${{ github.event.pull_request.title }}
|
||||
#${{ github.event.number }}](https://github.com/${{ github.repository }}/pull/${{ github.event.number }})
|
||||
was merged into master by ${{ github.actor }}
|
||||
was merged into main by ${{ github.actor }}
|
||||
|
||||
16
.github/workflows/notify_weekly.yaml
vendored
16
.github/workflows/notify_weekly.yaml
vendored
@@ -2,8 +2,8 @@ name: Weekly Github Update
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every week on Thursday @ 13:00
|
||||
- cron: "0 13 * * 4"
|
||||
# Every week on Thursday @ 10:00
|
||||
- cron: "0 10 * * 4"
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
send-mattermost-message:
|
||||
@@ -13,15 +13,13 @@ jobs:
|
||||
- name: Create Mattermost Message
|
||||
run: |
|
||||
DATE=$(date --date="7 days ago" +"%Y-%m-%d")
|
||||
echo "MERGED_URL=https://github.com/pulls?q=merged%3A%3E${DATE}+org%3Acertbot" >> $GITHUB_ENV
|
||||
echo "UPDATED_URL=https://github.com/pulls?q=updated%3A%3E${DATE}+org%3Acertbot" >> $GITHUB_ENV
|
||||
echo "ASSIGNED_PRS=https://github.com/pulls?q=is%3Apr+is%3Aopen+updated%3A%3E%3D${DATE}+assignee%3A*+user%3Acertbot" >> $GITHUB_ENV
|
||||
echo "UPDATED_URL=https://github.com/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-desc+updated%3A%3E%3D${DATE}+user%3Acertbot" >> $GITHUB_ENV
|
||||
- uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
|
||||
MATTERMOST_CHANNEL: private-certbot
|
||||
TEXT: |
|
||||
## Updates Across Certbot Repos
|
||||
- Certbot team members SHOULD look at: [link](${{ env.MERGED_URL }})
|
||||
- Certbot team members MAY also want to look at: [link](${{ env.UPDATED_URL }})
|
||||
- Want to Discuss something today? Place it [here](https://docs.google.com/document/d/17YMUbtC1yg6MfiTMwT8zVm9LmO-cuGVBom0qFn8XJBM/edit?usp=sharing) and we can meet today on Zoom.
|
||||
- The key words SHOULD and MAY in this message are to be interpreted as described in [RFC 8147](https://www.rfc-editor.org/rfc/rfc8174).
|
||||
## Updates In the Past Week
|
||||
- Most commented in the last week: [link](${{ env.UPDATED_URL }})
|
||||
- Updated (assigned) PRs in the last week: [link](${{ env.ASSIGNED_PRS }})
|
||||
|
||||
@@ -139,6 +139,7 @@ Authors
|
||||
* [John Reed](https://github.com/leerspace)
|
||||
* [Jonas Berlin](https://github.com/xkr47)
|
||||
* [Jonathan Herlin](https://github.com/Jonher937)
|
||||
* [Jonathan Vanasco](https://github.com/jvanasco)
|
||||
* [Jon Walsh](https://github.com/code-tree)
|
||||
* [Joona Hoikkala](https://github.com/joohoi)
|
||||
* [Josh McCullough](https://github.com/JoshMcCullough)
|
||||
|
||||
@@ -24,6 +24,7 @@ from acme.client import ClientV2
|
||||
|
||||
CERT_SAN_PEM = test_util.load_vector('cert-san.pem')
|
||||
CSR_MIXED_PEM = test_util.load_vector('csr-mixed.pem')
|
||||
CSR_NO_SANS_PEM = test_util.load_vector('csr-nosans.pem')
|
||||
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
|
||||
|
||||
DIRECTORY_V2 = messages.Directory({
|
||||
@@ -97,6 +98,10 @@ class ClientV2Test(unittest.TestCase):
|
||||
body=self.order,
|
||||
uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1',
|
||||
authorizations=[self.authzr, self.authzr2], csr_pem=CSR_MIXED_PEM)
|
||||
self.orderr2 = messages.OrderResource(
|
||||
body=self.order,
|
||||
uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1',
|
||||
authorizations=[self.authzr, self.authzr2], csr_pem=CSR_NO_SANS_PEM)
|
||||
|
||||
def test_new_account(self):
|
||||
self.response.status_code = http_client.CREATED
|
||||
@@ -158,6 +163,10 @@ class ClientV2Test(unittest.TestCase):
|
||||
mock_post_as_get.side_effect = (authz_response, authz_response2)
|
||||
assert self.client.new_order(CSR_MIXED_PEM) == self.orderr
|
||||
|
||||
with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get:
|
||||
mock_post_as_get.side_effect = (authz_response, authz_response2)
|
||||
assert self.client.new_order(CSR_NO_SANS_PEM) == self.orderr2
|
||||
|
||||
def test_answer_challege(self):
|
||||
self.response.links['up'] = {'url': self.challr.authzr_uri}
|
||||
self.response.json.return_value = self.challr.body.to_json()
|
||||
|
||||
@@ -12,11 +12,21 @@ import unittest
|
||||
import josepy as jose
|
||||
import OpenSSL
|
||||
import pytest
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, x25519
|
||||
|
||||
from acme import errors
|
||||
from acme._internal.tests import test_util
|
||||
|
||||
|
||||
class FormatTest(unittest.TestCase):
|
||||
def test_to_cryptography_encoding(self):
|
||||
from acme.crypto_util import Format
|
||||
assert Format.DER.to_cryptography_encoding() == serialization.Encoding.DER
|
||||
assert Format.PEM.to_cryptography_encoding() == serialization.Encoding.PEM
|
||||
|
||||
|
||||
class SSLSocketAndProbeSNITest(unittest.TestCase):
|
||||
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
|
||||
|
||||
@@ -177,48 +187,6 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
||||
['chicago-cubs.venafi.example', 'cubs.venafi.example']
|
||||
|
||||
|
||||
class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase):
|
||||
"""Test for acme.crypto_util._pyopenssl_cert_or_req_san_ip."""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, loader, name):
|
||||
# pylint: disable=protected-access
|
||||
from acme.crypto_util import _pyopenssl_cert_or_req_san_ip
|
||||
return _pyopenssl_cert_or_req_san_ip(loader(name))
|
||||
|
||||
def _call_cert(self, name):
|
||||
return self._call(test_util.load_cert, name)
|
||||
|
||||
def _call_csr(self, name):
|
||||
return self._call(test_util.load_csr, name)
|
||||
|
||||
def test_cert_no_sans(self):
|
||||
assert self._call_cert('cert.pem') == []
|
||||
|
||||
def test_csr_no_sans(self):
|
||||
assert self._call_csr('csr-nosans.pem') == []
|
||||
|
||||
def test_cert_domain_sans(self):
|
||||
assert self._call_cert('cert-san.pem') == []
|
||||
|
||||
def test_csr_domain_sans(self):
|
||||
assert self._call_csr('csr-san.pem') == []
|
||||
|
||||
def test_cert_ip_two_sans(self):
|
||||
assert self._call_cert('cert-ipsans.pem') == ['192.0.2.145', '203.0.113.1']
|
||||
|
||||
def test_csr_ip_two_sans(self):
|
||||
assert self._call_csr('csr-ipsans.pem') == ['192.0.2.145', '203.0.113.1']
|
||||
|
||||
def test_csr_ipv6_sans(self):
|
||||
assert self._call_csr('csr-ipv6sans.pem') == \
|
||||
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']
|
||||
|
||||
def test_cert_ipv6_sans(self):
|
||||
assert self._call_cert('cert-ipv6sans.pem') == \
|
||||
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']
|
||||
|
||||
|
||||
class GenSsCertTest(unittest.TestCase):
|
||||
"""Test for gen_ss_cert (generation of self-signed cert)."""
|
||||
|
||||
@@ -250,79 +218,74 @@ class MakeCSRTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def _call_with_key(cls, *args, **kwargs):
|
||||
privkey = OpenSSL.crypto.PKey()
|
||||
privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
|
||||
privkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey)
|
||||
privkey = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
privkey_pem = privkey.private_bytes(
|
||||
serialization.Encoding.PEM,
|
||||
serialization.PrivateFormat.PKCS8,
|
||||
serialization.NoEncryption(),
|
||||
)
|
||||
from acme.crypto_util import make_csr
|
||||
|
||||
return make_csr(privkey_pem, *args, **kwargs)
|
||||
|
||||
def test_make_csr(self):
|
||||
csr_pem = self._call_with_key(["a.example", "b.example"])
|
||||
assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem
|
||||
assert b'--END CERTIFICATE REQUEST--' in csr_pem
|
||||
csr = OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
|
||||
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't
|
||||
# have a get_extensions() method, so we skip this test if the method
|
||||
# isn't available.
|
||||
if hasattr(csr, 'get_extensions'):
|
||||
assert len(csr.get_extensions()) == 1
|
||||
assert csr.get_extensions()[0].get_data() == \
|
||||
OpenSSL.crypto.X509Extension(
|
||||
b'subjectAltName',
|
||||
critical=False,
|
||||
value=b'DNS:a.example, DNS:b.example',
|
||||
).get_data()
|
||||
assert b"--BEGIN CERTIFICATE REQUEST--" in csr_pem
|
||||
assert b"--END CERTIFICATE REQUEST--" in csr_pem
|
||||
csr = x509.load_pem_x509_csr(csr_pem)
|
||||
|
||||
assert len(csr.extensions) == 1
|
||||
assert list(
|
||||
csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
|
||||
) == [
|
||||
x509.DNSName("a.example"),
|
||||
x509.DNSName("b.example"),
|
||||
]
|
||||
|
||||
def test_make_csr_ip(self):
|
||||
csr_pem = self._call_with_key(["a.example"], False, [ipaddress.ip_address('127.0.0.1'), ipaddress.ip_address('::1')])
|
||||
assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem
|
||||
assert b'--END CERTIFICATE REQUEST--' in csr_pem
|
||||
csr = OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
|
||||
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't
|
||||
# have a get_extensions() method, so we skip this test if the method
|
||||
# isn't available.
|
||||
if hasattr(csr, 'get_extensions'):
|
||||
assert len(csr.get_extensions()) == 1
|
||||
assert csr.get_extensions()[0].get_data() == \
|
||||
OpenSSL.crypto.X509Extension(
|
||||
b'subjectAltName',
|
||||
critical=False,
|
||||
value=b'DNS:a.example, IP:127.0.0.1, IP:::1',
|
||||
).get_data()
|
||||
# for IP san it's actually need to be octet-string,
|
||||
# but somewhere downstream thankfully handle it for us
|
||||
csr_pem = self._call_with_key(
|
||||
["a.example"],
|
||||
False,
|
||||
[ipaddress.ip_address("127.0.0.1"), ipaddress.ip_address("::1")],
|
||||
)
|
||||
assert b"--BEGIN CERTIFICATE REQUEST--" in csr_pem
|
||||
assert b"--END CERTIFICATE REQUEST--" in csr_pem
|
||||
|
||||
csr = x509.load_pem_x509_csr(csr_pem)
|
||||
|
||||
assert len(csr.extensions) == 1
|
||||
assert list(
|
||||
csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
|
||||
) == [
|
||||
x509.DNSName("a.example"),
|
||||
x509.IPAddress(ipaddress.ip_address("127.0.0.1")),
|
||||
x509.IPAddress(ipaddress.ip_address("::1")),
|
||||
]
|
||||
|
||||
def test_make_csr_must_staple(self):
|
||||
csr_pem = self._call_with_key(["a.example"], must_staple=True)
|
||||
csr = OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
|
||||
csr = x509.load_pem_x509_csr(csr_pem)
|
||||
|
||||
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't
|
||||
# have a get_extensions() method, so we skip this test if the method
|
||||
# isn't available.
|
||||
if hasattr(csr, 'get_extensions'):
|
||||
assert len(csr.get_extensions()) == 2
|
||||
# NOTE: Ideally we would filter by the TLS Feature OID, but
|
||||
# OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID,
|
||||
# and the shortname field is just "UNDEF"
|
||||
must_staple_exts = [e for e in csr.get_extensions()
|
||||
if e.get_data() == b"0\x03\x02\x01\x05"]
|
||||
assert len(must_staple_exts) == 1, \
|
||||
"Expected exactly one Must Staple extension"
|
||||
assert len(csr.extensions) == 2
|
||||
assert list(csr.extensions.get_extension_for_class(x509.TLSFeature).value) == [
|
||||
x509.TLSFeatureType.status_request
|
||||
]
|
||||
|
||||
def test_make_csr_without_hostname(self):
|
||||
with pytest.raises(ValueError):
|
||||
self._call_with_key()
|
||||
|
||||
def test_make_csr_correct_version(self):
|
||||
csr_pem = self._call_with_key(["a.example"])
|
||||
csr = OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
|
||||
def test_make_csr_invalid_key_type(self):
|
||||
privkey = x25519.X25519PrivateKey.generate()
|
||||
privkey_pem = privkey.private_bytes(
|
||||
serialization.Encoding.PEM,
|
||||
serialization.PrivateFormat.PKCS8,
|
||||
serialization.NoEncryption(),
|
||||
)
|
||||
from acme.crypto_util import make_csr
|
||||
|
||||
assert csr.get_version() == 0, \
|
||||
"Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4"
|
||||
with pytest.raises(ValueError):
|
||||
make_csr(privkey_pem, ["a.example"])
|
||||
|
||||
|
||||
class DumpPyopensslChainTest(unittest.TestCase):
|
||||
|
||||
@@ -21,7 +21,7 @@ class HeaderTest(unittest.TestCase):
|
||||
except (ValueError, TypeError):
|
||||
assert True
|
||||
else:
|
||||
assert False # pragma: no cover
|
||||
pytest.fail("Exception from jose.b64decode wasn't raised") # pragma: no cover
|
||||
|
||||
def test_nonce_decoder(self):
|
||||
from acme.jws import Header
|
||||
|
||||
@@ -193,7 +193,7 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
|
||||
|
||||
from acme.standalone import BaseDualNetworkedServers
|
||||
|
||||
mock_bind.side_effect = socket.error(EADDRINUSE, "Fake addr in use error")
|
||||
mock_bind.side_effect = OSError(EADDRINUSE, "Fake addr in use error")
|
||||
|
||||
with pytest.raises(socket.error) as exc_info:
|
||||
BaseDualNetworkedServers(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
.. warning:: This module is not part of the public API.
|
||||
|
||||
"""
|
||||
import importlib.resources
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -12,16 +13,11 @@ import josepy as jose
|
||||
from josepy.util import ComparableECKey
|
||||
from OpenSSL import crypto
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
|
||||
def load_vector(*names):
|
||||
"""Load contents of a test vector."""
|
||||
# luckily, resource_string opens file in binary mode
|
||||
vector_ref = importlib_resources.files(__package__).joinpath('testdata', *names)
|
||||
vector_ref = importlib.resources.files(__package__).joinpath('testdata', *names)
|
||||
return vector_ref.read_bytes()
|
||||
|
||||
|
||||
|
||||
28
acme/acme/_internal/tests/testdata/csr-mixed.pem
vendored
28
acme/acme/_internal/tests/testdata/csr-mixed.pem
vendored
@@ -1,16 +1,16 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICdjCCAV4CAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMXq
|
||||
v1y8EIcCbaUIzCtOcLkLS0MJ35oS+6DmV5WB1A0cIk6YrjsHIsY2lwMm13BWIvmw
|
||||
tY+Y6n0rr7eViNx5ZRGHpHEI/TL3Neb+VefTydL5CgvK3dd4ex2kSbTaed3fmpOx
|
||||
qMajEduwNcZPCcmoEXPkfrCP8w2vKQUkQ+JRPcdX1nTuzticeRP5B7YCmJsmxkEh
|
||||
Y0tzzZ+NIRDARoYNofefY86h3e5q66gtJxccNchmIM3YQahhg5n3Xoo8hGfM/TIc
|
||||
R7ncCBCLO6vtqo0QFva/NQODrgOmOsmgvqPkUWQFdZfWM8yIaU826dktx0CPB78t
|
||||
TudnJ1rBRvGsjHMsZikCAwEAAaAxMC8GCSqGSIb3DQEJDjEiMCAwHgYDVR0RBBcw
|
||||
FYINYS5leGVtcGxlLmNvbYcEwAACbzANBgkqhkiG9w0BAQsFAAOCAQEAdGMcRCxq
|
||||
1X09gn1TNdMt64XUv+wdJCKDaJ+AgyIJj7QvVw8H5k7dOnxS4I+a/yo4jE+LDl2/
|
||||
AuHcBLFEI4ddewdJSMrTNZjuRYuOdr3KP7fL7MffICSBi45vw5EOXg0tnjJCEiKu
|
||||
6gcJgbLSP5JMMd7Haf33Q/VWsmHofR3VwOMdrnakwAU3Ff5WTuXTNVhL1kT/uLFX
|
||||
yW1ru6BF4unwNqSR2UeulljpNfRBsiN4zJK11W6n9KT0NkBr9zY5WCM4sW7i8k9V
|
||||
TeypWGo3jBKzYAGeuxZsB97U77jZ2lrGdBLZKfbcjnTeRVqCvCRrui4El7UGYFmj
|
||||
7s6OJyWx5DSV8w==
|
||||
MIICdjCCAV4CAQAwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANoV
|
||||
T1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svNPSa+oPTK
|
||||
7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm7Gj6m2Ez
|
||||
pSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFnxvvOjBYo
|
||||
p7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTDg7P4UAuF
|
||||
kejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1RAeEeRTk
|
||||
h0WjUfltoem/5f8bIdsCAwEAAaAxMC8GCSqGSIb3DQEJDjEiMCAwHgYDVR0RBBcw
|
||||
FYINYS5leGVtcGxlLmNvbYcEwAACbzANBgkqhkiG9w0BAQsFAAOCAQEAQ7n/hYen
|
||||
5INHlcslHPYCQ/BAbX6Ou+Y8hUu8puWNVpE2OM95L2C87jbWwTmCRnkFBwtyoNqo
|
||||
j3DXVW2RYv8y/exq7V6Y5LtpHTgwfugINJ3XlcVzA4Vnf1xqOxv3kwejkq74RuXn
|
||||
xd5N28srgiFqb0e4tOAWVI8Tw27bgBqjoXl0QDFPZpctqUia5bcDJ9WzNSM7VaO1
|
||||
CBNGHBRz+zL8sqoqJA4HV58tjcgzl+1RtGM+iUHxXpnH+aCNKWIUINrAzIm4Sm00
|
||||
93RJjhb1kdNR0BC7ikWVbAWaVviHdvATK/RfpmhWDqfEaNgBpvT91GnkhpzctSFD
|
||||
ro0yCUUXXrIr0w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
|
||||
@@ -15,6 +15,7 @@ from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
import josepy as jose
|
||||
from OpenSSL import crypto
|
||||
@@ -89,7 +90,7 @@ class _TokenChallenge(Challenge):
|
||||
:ivar bytes token:
|
||||
|
||||
"""
|
||||
TOKEN_SIZE = 128 / 8 # Based on the entropy value from the spec
|
||||
TOKEN_SIZE = 128 // 8 # Based on the entropy value from the spec
|
||||
"""Minimum size of the :attr:`token` in bytes."""
|
||||
|
||||
# TODO: acme-spec doesn't specify token as base64-encoded value
|
||||
@@ -460,34 +461,39 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
|
||||
return crypto_util.probe_sni(host=host.encode(), port=port, name=domain.encode(),
|
||||
alpn_protocols=[self.ACME_TLS_1_PROTOCOL])
|
||||
|
||||
def verify_cert(self, domain: str, cert: crypto.X509) -> bool:
|
||||
def verify_cert(self, domain: str, cert: Union[x509.Certificate, crypto.X509]) -> bool:
|
||||
"""Verify tls-alpn-01 challenge certificate.
|
||||
|
||||
:param str domain: Domain name being validated.
|
||||
:param OpensSSL.crypto.X509 cert: Challenge certificate.
|
||||
:param cert: Challenge certificate.
|
||||
:type cert: `cryptography.x509.Certificate` or `OpenSSL.crypto.X509`
|
||||
|
||||
:returns: Whether the certificate was successfully verified.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
names = crypto_util._pyopenssl_cert_or_req_all_names(cert)
|
||||
# Type ignore needed due to
|
||||
# https://github.com/pyca/pyopenssl/issues/730.
|
||||
logger.debug('Certificate %s. SANs: %s',
|
||||
cert.digest('sha256'), names)
|
||||
if not isinstance(cert, x509.Certificate):
|
||||
cert = cert.to_cryptography()
|
||||
|
||||
names = crypto_util.get_names_from_subject_and_extensions(
|
||||
cert.subject, cert.extensions
|
||||
)
|
||||
logger.debug(
|
||||
"Certificate %s. SANs: %s", cert.fingerprint(hashes.SHA256()), names
|
||||
)
|
||||
if len(names) != 1 or names[0].lower() != domain.lower():
|
||||
return False
|
||||
|
||||
for i in range(cert.get_extension_count()):
|
||||
ext = cert.get_extension(i)
|
||||
# FIXME: assume this is the ACME extension. Currently there is no
|
||||
# way to get full OID of an unknown extension from pyopenssl.
|
||||
if ext.get_short_name() == b'UNDEF':
|
||||
data = ext.get_data()
|
||||
return data == self.h
|
||||
try:
|
||||
ext = cert.extensions.get_extension_for_oid(
|
||||
x509.ObjectIdentifier(self.ID_PE_ACME_IDENTIFIER_V1.decode())
|
||||
)
|
||||
except x509.ExtensionNotFound:
|
||||
return False
|
||||
|
||||
return False
|
||||
# This is for the type checker.
|
||||
assert isinstance(ext.value, x509.UnrecognizedExtension)
|
||||
return ext.value.value == self.h
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def simple_verify(self, chall: 'TLSALPN01', domain: str, account_public_key: jose.JWK,
|
||||
|
||||
@@ -15,6 +15,8 @@ from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from cryptography import x509
|
||||
|
||||
import josepy as jose
|
||||
import OpenSSL
|
||||
import requests
|
||||
@@ -121,18 +123,21 @@ class ClientV2:
|
||||
:returns: The newly created order.
|
||||
:rtype: OrderResource
|
||||
"""
|
||||
csr = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr_pem)
|
||||
# pylint: disable=protected-access
|
||||
dnsNames = crypto_util._pyopenssl_cert_or_req_all_names(csr)
|
||||
ipNames = crypto_util._pyopenssl_cert_or_req_san_ip(csr)
|
||||
# ipNames is now []string
|
||||
csr = x509.load_pem_x509_csr(csr_pem)
|
||||
dnsNames = crypto_util.get_names_from_subject_and_extensions(csr.subject, csr.extensions)
|
||||
try:
|
||||
san_ext = csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
except x509.ExtensionNotFound:
|
||||
ipNames = []
|
||||
else:
|
||||
ipNames = san_ext.value.get_values_for_type(x509.IPAddress)
|
||||
identifiers = []
|
||||
for name in dnsNames:
|
||||
identifiers.append(messages.Identifier(typ=messages.IDENTIFIER_FQDN,
|
||||
value=name))
|
||||
for ips in ipNames:
|
||||
for ip in ipNames:
|
||||
identifiers.append(messages.Identifier(typ=messages.IDENTIFIER_IP,
|
||||
value=ips))
|
||||
value=str(ip)))
|
||||
order = messages.NewOrder(identifiers=identifiers)
|
||||
response = self._post(self.directory['newOrder'], order)
|
||||
body = messages.Order.from_json(response.json())
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""Crypto utilities."""
|
||||
import binascii
|
||||
import contextlib
|
||||
import enum
|
||||
import ipaddress
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import typing
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
@@ -16,6 +17,9 @@ from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, rsa, ec, ed25519, ed448
|
||||
import josepy as jose
|
||||
from OpenSSL import crypto
|
||||
from OpenSSL import SSL
|
||||
@@ -34,6 +38,24 @@ logger = logging.getLogger(__name__)
|
||||
_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD
|
||||
|
||||
|
||||
class Format(enum.IntEnum):
|
||||
"""File format to be used when parsing or serializing X.509 structures.
|
||||
|
||||
Backwards compatible with the `FILETYPE_ASN1` and `FILETYPE_PEM` constants
|
||||
from pyOpenSSL.
|
||||
"""
|
||||
DER = crypto.FILETYPE_ASN1
|
||||
PEM = crypto.FILETYPE_PEM
|
||||
|
||||
def to_cryptography_encoding(self) -> serialization.Encoding:
|
||||
"""Converts the Format to the corresponding cryptography `Encoding`.
|
||||
"""
|
||||
if self == Format.DER:
|
||||
return serialization.Encoding.DER
|
||||
else:
|
||||
return serialization.Encoding.PEM
|
||||
|
||||
|
||||
class _DefaultCertSelection:
|
||||
def __init__(self, certs: Mapping[bytes, Tuple[crypto.PKey, crypto.X509]]):
|
||||
self.certs = certs
|
||||
@@ -73,13 +95,9 @@ class SSLSocket: # pylint: disable=too-few-public-methods
|
||||
raise ValueError("Neither cert_selection or certs specified.")
|
||||
if cert_selection and certs:
|
||||
raise ValueError("Both cert_selection and certs specified.")
|
||||
actual_cert_selection: Union[_DefaultCertSelection,
|
||||
Optional[Callable[[SSL.Connection],
|
||||
Optional[Tuple[crypto.PKey,
|
||||
crypto.X509]]]]] = cert_selection
|
||||
if actual_cert_selection is None:
|
||||
actual_cert_selection = _DefaultCertSelection(certs if certs else {})
|
||||
self.cert_selection = actual_cert_selection
|
||||
if cert_selection is None:
|
||||
cert_selection = _DefaultCertSelection(certs if certs else {})
|
||||
self.cert_selection = cert_selection
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
return getattr(self.sock, name)
|
||||
@@ -131,7 +149,7 @@ class SSLSocket: # pylint: disable=too-few-public-methods
|
||||
# in the standard library. This is useful when this object is
|
||||
# used by code which expects a standard socket such as
|
||||
# socketserver in the standard library.
|
||||
raise socket.error(error)
|
||||
raise OSError(error)
|
||||
|
||||
def accept(self) -> Tuple[FakeConnection, Any]: # pylint: disable=missing-function-docstring
|
||||
sock, addr = self.sock.accept()
|
||||
@@ -155,7 +173,7 @@ class SSLSocket: # pylint: disable=too-few-public-methods
|
||||
except SSL.Error as error:
|
||||
# _pick_certificate_cb might have returned without
|
||||
# creating SSL context (wrong server name)
|
||||
raise socket.error(error)
|
||||
raise OSError(error)
|
||||
|
||||
return ssl_sock, addr
|
||||
except:
|
||||
@@ -203,7 +221,7 @@ def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, #
|
||||
)
|
||||
socket_tuple: Tuple[bytes, int] = (host, port)
|
||||
sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore[arg-type]
|
||||
except socket.error as error:
|
||||
except OSError as error:
|
||||
raise errors.Error(error)
|
||||
|
||||
with contextlib.closing(sock) as client:
|
||||
@@ -222,77 +240,118 @@ def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, #
|
||||
return cert
|
||||
|
||||
|
||||
def make_csr(private_key_pem: bytes, domains: Optional[Union[Set[str], List[str]]] = None,
|
||||
must_staple: bool = False,
|
||||
ipaddrs: Optional[List[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]] = None
|
||||
) -> bytes:
|
||||
def make_csr(
|
||||
private_key_pem: bytes,
|
||||
domains: Optional[Union[Set[str], List[str]]] = None,
|
||||
must_staple: bool = False,
|
||||
ipaddrs: Optional[List[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]] = None,
|
||||
) -> bytes:
|
||||
"""Generate a CSR containing domains or IPs as subjectAltNames.
|
||||
|
||||
Parameters are ordered this way for backwards compatibility when called using positional
|
||||
arguments.
|
||||
|
||||
:param buffer private_key_pem: Private key, in PEM PKCS#8 format.
|
||||
:param list domains: List of DNS names to include in subjectAltNames of CSR.
|
||||
:param bool must_staple: Whether to include the TLS Feature extension (aka
|
||||
OCSP Must Staple: https://tools.ietf.org/html/rfc7633).
|
||||
:param list ipaddrs: List of IPaddress(type ipaddress.IPv4Address or ipaddress.IPv6Address)
|
||||
names to include in subbjectAltNames of CSR.
|
||||
params ordered this way for backward competablity when called by positional argument.
|
||||
names to include in subbjectAltNames of CSR.
|
||||
|
||||
:returns: buffer PEM-encoded Certificate Signing Request.
|
||||
|
||||
"""
|
||||
private_key = crypto.load_privatekey(
|
||||
crypto.FILETYPE_PEM, private_key_pem)
|
||||
csr = crypto.X509Req()
|
||||
sanlist = []
|
||||
# if domain or ip list not supplied make it empty list so it's easier to iterate
|
||||
private_key = serialization.load_pem_private_key(private_key_pem, password=None)
|
||||
# There are a few things that aren't valid for x509 signing. mypy
|
||||
# complains if we don't check.
|
||||
if not isinstance(
|
||||
private_key,
|
||||
(
|
||||
dsa.DSAPrivateKey,
|
||||
rsa.RSAPrivateKey,
|
||||
ec.EllipticCurvePrivateKey,
|
||||
ed25519.Ed25519PrivateKey,
|
||||
ed448.Ed448PrivateKey,
|
||||
),
|
||||
):
|
||||
raise ValueError(f"Invalid private key type: {type(private_key)}")
|
||||
if domains is None:
|
||||
domains = []
|
||||
if ipaddrs is None:
|
||||
ipaddrs = []
|
||||
if len(domains)+len(ipaddrs) == 0:
|
||||
raise ValueError("At least one of domains or ipaddrs parameter need to be not empty")
|
||||
for address in domains:
|
||||
sanlist.append('DNS:' + address)
|
||||
for ips in ipaddrs:
|
||||
sanlist.append('IP:' + ips.exploded)
|
||||
# make sure its ascii encoded
|
||||
san_string = ', '.join(sanlist).encode('ascii')
|
||||
# for IP san it's actually need to be octet-string,
|
||||
# but somewhere downsteam thankfully handle it for us
|
||||
extensions = [
|
||||
crypto.X509Extension(
|
||||
b'subjectAltName',
|
||||
if len(domains) + len(ipaddrs) == 0:
|
||||
raise ValueError(
|
||||
"At least one of domains or ipaddrs parameter need to be not empty"
|
||||
)
|
||||
|
||||
builder = (
|
||||
x509.CertificateSigningRequestBuilder()
|
||||
.subject_name(x509.Name([]))
|
||||
.add_extension(
|
||||
x509.SubjectAlternativeName(
|
||||
[x509.DNSName(d) for d in domains]
|
||||
+ [x509.IPAddress(i) for i in ipaddrs]
|
||||
),
|
||||
critical=False,
|
||||
value=san_string
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
if must_staple:
|
||||
extensions.append(crypto.X509Extension(
|
||||
b"1.3.6.1.5.5.7.1.24",
|
||||
builder = builder.add_extension(
|
||||
# "status_request" is the feature commonly known as OCSP
|
||||
# Must-Staple
|
||||
x509.TLSFeature([x509.TLSFeatureType.status_request]),
|
||||
critical=False,
|
||||
value=b"DER:30:03:02:01:05"))
|
||||
csr.add_extensions(extensions)
|
||||
csr.set_pubkey(private_key)
|
||||
# RFC 2986 Section 4.1 only defines version 0
|
||||
csr.set_version(0)
|
||||
csr.sign(private_key, 'sha256')
|
||||
return crypto.dump_certificate_request(
|
||||
crypto.FILETYPE_PEM, csr)
|
||||
)
|
||||
|
||||
csr = builder.sign(private_key, hashes.SHA256())
|
||||
return csr.public_bytes(serialization.Encoding.PEM)
|
||||
|
||||
|
||||
def get_names_from_subject_and_extensions(
|
||||
subject: x509.Name, exts: x509.Extensions
|
||||
) -> List[str]:
|
||||
"""Gets all DNS SAN names as well as the first Common Name from subject.
|
||||
|
||||
:param subject: Name of the x509 object, which may include Common Name
|
||||
:type subject: `cryptography.x509.Name`
|
||||
:param exts: Extensions of the x509 object, which may include SANs
|
||||
:type exts: `cryptography.x509.Extensions`
|
||||
|
||||
:returns: List of DNS Subject Alternative Names and first Common Name
|
||||
:rtype: `list` of `str`
|
||||
"""
|
||||
# We know these are always `str` because `bytes` is only possible for
|
||||
# other OIDs.
|
||||
cns = [
|
||||
typing.cast(str, c.value)
|
||||
for c in subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
||||
]
|
||||
try:
|
||||
san_ext = exts.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
except x509.ExtensionNotFound:
|
||||
dns_names = []
|
||||
else:
|
||||
dns_names = san_ext.value.get_values_for_type(x509.DNSName)
|
||||
|
||||
if not cns:
|
||||
return dns_names
|
||||
else:
|
||||
# We only include the first CN, if there are multiple. This matches
|
||||
# the behavior of the previously implementation using pyOpenSSL.
|
||||
return [cns[0]] + [d for d in dns_names if d != cns[0]]
|
||||
|
||||
|
||||
def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req: Union[crypto.X509, crypto.X509Req]
|
||||
) -> List[str]:
|
||||
# unlike its name this only outputs DNS names, other type of idents will ignored
|
||||
common_name = loaded_cert_or_req.get_subject().CN
|
||||
sans = _pyopenssl_cert_or_req_san(loaded_cert_or_req)
|
||||
|
||||
if common_name is None:
|
||||
return sans
|
||||
return [common_name] + [d for d in sans if d != common_name]
|
||||
cert_or_req = loaded_cert_or_req.to_cryptography()
|
||||
return get_names_from_subject_and_extensions(
|
||||
cert_or_req.subject, cert_or_req.extensions
|
||||
)
|
||||
|
||||
|
||||
def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, crypto.X509Req]) -> List[str]:
|
||||
"""Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
|
||||
|
||||
.. todo:: Implement directly in PyOpenSSL!
|
||||
|
||||
.. note:: Although this is `acme` internal API, it is used by
|
||||
`letsencrypt`.
|
||||
|
||||
@@ -303,67 +362,13 @@ def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, crypto.X509Req])
|
||||
:rtype: `list` of `str`
|
||||
|
||||
"""
|
||||
# This function finds SANs with dns name
|
||||
exts = cert_or_req.to_cryptography().extensions
|
||||
try:
|
||||
san_ext = exts.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
except x509.ExtensionNotFound:
|
||||
return []
|
||||
|
||||
# constants based on PyOpenSSL certificate/CSR text dump
|
||||
part_separator = ":"
|
||||
prefix = "DNS" + part_separator
|
||||
|
||||
sans_parts = _pyopenssl_extract_san_list_raw(cert_or_req)
|
||||
|
||||
return [part.split(part_separator)[1]
|
||||
for part in sans_parts if part.startswith(prefix)]
|
||||
|
||||
|
||||
def _pyopenssl_cert_or_req_san_ip(cert_or_req: Union[crypto.X509, crypto.X509Req]) -> List[str]:
|
||||
"""Get Subject Alternative Names IPs from certificate or CSR using pyOpenSSL.
|
||||
|
||||
:param cert_or_req: Certificate or CSR.
|
||||
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
|
||||
|
||||
:returns: A list of Subject Alternative Names that are IP Addresses.
|
||||
:rtype: `list` of `str`. note that this returns as string, not IPaddress object
|
||||
|
||||
"""
|
||||
|
||||
# constants based on PyOpenSSL certificate/CSR text dump
|
||||
part_separator = ":"
|
||||
prefix = "IP Address" + part_separator
|
||||
|
||||
sans_parts = _pyopenssl_extract_san_list_raw(cert_or_req)
|
||||
|
||||
return [part[len(prefix):] for part in sans_parts if part.startswith(prefix)]
|
||||
|
||||
|
||||
def _pyopenssl_extract_san_list_raw(cert_or_req: Union[crypto.X509, crypto.X509Req]) -> List[str]:
|
||||
"""Get raw SAN string from cert or csr, parse it as UTF-8 and return.
|
||||
|
||||
:param cert_or_req: Certificate or CSR.
|
||||
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
|
||||
|
||||
:returns: raw san strings, parsed byte as utf-8
|
||||
:rtype: `list` of `str`
|
||||
|
||||
"""
|
||||
# This function finds SANs by dumping the certificate/CSR to text and
|
||||
# searching for "X509v3 Subject Alternative Name" in the text. This method
|
||||
# is used to because in PyOpenSSL version <0.17 `_subjectAltNameString` methods are
|
||||
# not able to Parse IP Addresses in subjectAltName string.
|
||||
|
||||
if isinstance(cert_or_req, crypto.X509):
|
||||
# pylint: disable=line-too-long
|
||||
text = crypto.dump_certificate(crypto.FILETYPE_TEXT, cert_or_req).decode('utf-8')
|
||||
else:
|
||||
text = crypto.dump_certificate_request(crypto.FILETYPE_TEXT, cert_or_req).decode('utf-8')
|
||||
# WARNING: this function does not support multiple SANs extensions.
|
||||
# Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
|
||||
raw_san = re.search(r"X509v3 Subject Alternative Name:(?: critical)?\s*(.*)", text)
|
||||
|
||||
parts_separator = ", "
|
||||
# WARNING: this function assumes that no SAN can include
|
||||
# parts_separator, hence the split!
|
||||
sans_parts = [] if raw_san is None else raw_san.group(1).split(parts_separator)
|
||||
return sans_parts
|
||||
return san_ext.value.get_values_for_type(x509.DNSName)
|
||||
|
||||
|
||||
def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
|
||||
@@ -433,7 +438,7 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
|
||||
|
||||
|
||||
def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X509]],
|
||||
filetype: int = crypto.FILETYPE_PEM) -> bytes:
|
||||
filetype: Union[Format, int] = Format.PEM) -> bytes:
|
||||
"""Dump certificate chain into a bundle.
|
||||
|
||||
:param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
|
||||
@@ -446,12 +451,14 @@ def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X50
|
||||
# XXX: returns empty string when no chain is available, which
|
||||
# shuts up RenewableCert, but might not be the best solution...
|
||||
|
||||
filetype = Format(filetype)
|
||||
def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes:
|
||||
if isinstance(cert, jose.ComparableX509):
|
||||
if isinstance(cert.wrapped, crypto.X509Req):
|
||||
raise errors.Error("Unexpected CSR provided.") # pragma: no cover
|
||||
cert = cert.wrapped
|
||||
return crypto.dump_certificate(filetype, cert)
|
||||
|
||||
return cert.to_cryptography().public_bytes(filetype.to_cryptography_encoding())
|
||||
|
||||
# assumes that OpenSSL.crypto.dump_certificate includes ending
|
||||
# newline character
|
||||
|
||||
@@ -98,7 +98,7 @@ class BaseDualNetworkedServers:
|
||||
logger.debug(
|
||||
"Successfully bound to %s:%s using %s", new_address[0],
|
||||
new_address[1], "IPv6" if ip_version else "IPv4")
|
||||
except socket.error as e:
|
||||
except OSError as e:
|
||||
last_socket_err = e
|
||||
if self.servers:
|
||||
# Already bound using IPv6.
|
||||
@@ -121,7 +121,7 @@ class BaseDualNetworkedServers:
|
||||
if last_socket_err:
|
||||
raise last_socket_err
|
||||
else: # pragma: no cover
|
||||
raise socket.error("Could not bind to IPv4 or IPv6.")
|
||||
raise OSError("Could not bind to IPv4 or IPv6.")
|
||||
|
||||
def serve_forever(self) -> None:
|
||||
"""Wraps socketserver.TCPServer.serve_forever"""
|
||||
|
||||
5
acme/docs/api/crypto_util.rst
Normal file
5
acme/docs/api/crypto_util.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
Crypto_util
|
||||
-----------
|
||||
|
||||
.. automodule:: acme.crypto_util
|
||||
:members:
|
||||
5
acme/docs/api/jws.rst
Normal file
5
acme/docs/api/jws.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
JWS
|
||||
---
|
||||
|
||||
.. automodule:: acme.jws
|
||||
:members:
|
||||
5
acme/docs/api/util.rst
Normal file
5
acme/docs/api/util.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
Util
|
||||
----
|
||||
|
||||
.. automodule:: acme.util
|
||||
:members:
|
||||
@@ -28,6 +28,7 @@ Workflow:
|
||||
from contextlib import contextmanager
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
import josepy as jose
|
||||
import OpenSSL
|
||||
@@ -68,10 +69,9 @@ def new_csr_comp(domain_name, pkey_pem=None):
|
||||
"""Create certificate signing request."""
|
||||
if pkey_pem is None:
|
||||
# Create private key.
|
||||
pkey = OpenSSL.crypto.PKey()
|
||||
pkey.generate_key(OpenSSL.crypto.TYPE_RSA, CERT_PKEY_BITS)
|
||||
pkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM,
|
||||
pkey)
|
||||
pkey = rsa.generate_private_key(public_exponent=65537, key_size=CERT_PKEY_BITS)
|
||||
pkey_pem = pkey.public_bytes(serialization.Encoding.PEM)
|
||||
|
||||
csr_pem = crypto_util.make_csr(pkey_pem, [domain_name])
|
||||
return pkey_pem, csr_pem
|
||||
|
||||
@@ -201,8 +201,10 @@ def example_http():
|
||||
# Revoke certificate
|
||||
|
||||
fullchain_com = jose.ComparableX509(
|
||||
OpenSSL.crypto.load_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, fullchain_pem))
|
||||
OpenSSL.crypto.X509.from_cryptography(
|
||||
x509.load_pem_x509_certificate(fullchain_pem)
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
client_acme.revoke(fullchain_com, 0) # revocation reason = 0
|
||||
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'cryptography>=3.2.1',
|
||||
@@ -15,7 +15,6 @@ install_requires = [
|
||||
'pyrfc3339',
|
||||
'pytz>=2019.3',
|
||||
'requests>=2.20.0',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
docs_extras = [
|
||||
@@ -24,15 +23,6 @@ docs_extras = [
|
||||
]
|
||||
|
||||
test_extras = [
|
||||
# In theory we could scope importlib_resources to env marker 'python_version<"3.9"'. But this
|
||||
# makes the pinning mechanism emit warnings when running `poetry lock` because in the corner
|
||||
# case of an extra dependency with env marker coming from a setup.py file, it generate the
|
||||
# invalid requirement 'importlib_resource>=1.3.1;python<=3.9;extra=="test"'.
|
||||
# To fix the issue, we do not pass the env marker. This is fine because:
|
||||
# - importlib_resources can be applied to any Python version,
|
||||
# - this is a "test" extra dependency for limited audience,
|
||||
# - it does not change anything at the end for the generated requirement files.
|
||||
'importlib_resources>=1.3.1',
|
||||
'pytest',
|
||||
'pytest-xdist',
|
||||
'typing-extensions',
|
||||
@@ -46,14 +36,13 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import atexit
|
||||
import binascii
|
||||
import fnmatch
|
||||
import importlib.resources
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import ExitStack
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
@@ -17,12 +17,6 @@ from certbot import errors
|
||||
from certbot import util
|
||||
from certbot.compat import os
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -257,6 +251,6 @@ def find_ssl_apache_conf(prefix: str) -> str:
|
||||
"""
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
ref = (importlib_resources.files("certbot_apache").joinpath("_internal")
|
||||
ref = (importlib.resources.files("certbot_apache").joinpath("_internal")
|
||||
.joinpath("tls_configs").joinpath("{0}-options-ssl-apache.conf".format(prefix)))
|
||||
return str(file_manager.enter_context(importlib_resources.as_file(ref)))
|
||||
return str(file_manager.enter_context(importlib.resources.as_file(ref)))
|
||||
|
||||
@@ -295,7 +295,7 @@ class ApacheConfigurator(common.Configurator):
|
||||
try:
|
||||
with open(ssl_module_location, mode="rb") as f:
|
||||
contents = f.read()
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
logger.debug(str(error), exc_info=True)
|
||||
return None
|
||||
return contents
|
||||
@@ -928,7 +928,7 @@ class ApacheConfigurator(common.Configurator):
|
||||
try:
|
||||
socket.inet_aton(addr.get_addr())
|
||||
return socket.gethostbyaddr(addr.get_addr())[0]
|
||||
except (socket.error, socket.herror, socket.timeout):
|
||||
except (OSError, socket.herror, socket.timeout):
|
||||
pass
|
||||
|
||||
return ""
|
||||
@@ -1523,7 +1523,7 @@ class ApacheConfigurator(common.Configurator):
|
||||
# activation (it's not included as default)
|
||||
if not self.parser.parsed_in_current(ssl_fp):
|
||||
self.parser.parse_file(ssl_fp)
|
||||
except IOError:
|
||||
except OSError:
|
||||
logger.critical("Error writing/reading to file in make_vhost_ssl", exc_info=True)
|
||||
raise errors.PluginError("Unable to write/read in make_vhost_ssl")
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
"""Apache plugin constants."""
|
||||
import atexit
|
||||
import sys
|
||||
import importlib.resources
|
||||
from contextlib import ExitStack
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
MOD_SSL_CONF_DEST = "options-ssl-apache.conf"
|
||||
"""Name of the mod_ssl config file as saved
|
||||
@@ -46,8 +42,8 @@ def _generate_augeas_lens_dir_static() -> str:
|
||||
# Python process, and will be automatically cleaned up on exit.
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
augeas_lens_dir_ref = importlib_resources.files("certbot_apache") / "_internal" / "augeas_lens"
|
||||
return str(file_manager.enter_context(importlib_resources.as_file(augeas_lens_dir_ref)))
|
||||
augeas_lens_dir_ref = importlib.resources.files("certbot_apache") / "_internal" / "augeas_lens"
|
||||
return str(file_manager.enter_context(importlib.resources.as_file(augeas_lens_dir_ref)))
|
||||
|
||||
AUGEAS_LENS_DIR = _generate_augeas_lens_dir_static()
|
||||
"""Path to the Augeas lens directory"""
|
||||
|
||||
@@ -153,7 +153,7 @@ class ApacheParser:
|
||||
try:
|
||||
# This is a noop save
|
||||
self.aug.save()
|
||||
except (RuntimeError, IOError):
|
||||
except (OSError, RuntimeError):
|
||||
self._log_save_errors(ex_errs)
|
||||
# Erase Save Notes
|
||||
self.configurator.save_notes = ""
|
||||
@@ -198,7 +198,7 @@ class ApacheParser:
|
||||
ex_errs = self.aug.match("/augeas//error")
|
||||
try:
|
||||
self.aug.save()
|
||||
except IOError:
|
||||
except OSError:
|
||||
self._log_save_errors(ex_errs)
|
||||
raise
|
||||
|
||||
|
||||
@@ -174,9 +174,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
assert "certbot.demo" in names
|
||||
|
||||
def test_get_bad_path(self):
|
||||
assert apache_util.get_file_path(None) == None
|
||||
assert apache_util.get_file_path("nonexistent") == None
|
||||
assert self.config._create_vhost("nonexistent") == None # pylint: disable=protected-access
|
||||
assert apache_util.get_file_path(None) is None
|
||||
assert apache_util.get_file_path("nonexistent") is None
|
||||
assert self.config._create_vhost("nonexistent") is None # pylint: disable=protected-access
|
||||
|
||||
def test_get_aug_internal_path(self):
|
||||
from certbot_apache._internal.apache_util import get_internal_aug_path
|
||||
@@ -303,7 +303,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# pylint: disable=protected-access
|
||||
assert self.vh_truth[3] == self.config._find_best_vhost("certbot.demo")
|
||||
assert self.vh_truth[0] == self.config._find_best_vhost("encryption-example.demo")
|
||||
assert self.config._find_best_vhost("does-not-exist.com") == None
|
||||
assert self.config._find_best_vhost("does-not-exist.com") is None
|
||||
|
||||
def test_find_best_vhost_variety(self):
|
||||
# pylint: disable=protected-access
|
||||
@@ -1417,7 +1417,7 @@ class AugeasVhostsTest(util.ApacheTest):
|
||||
self.config.parser.aug.match.side_effect = RuntimeError
|
||||
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
|
||||
chosen_vhost = self.config._create_vhost(path)
|
||||
assert None == chosen_vhost
|
||||
assert chosen_vhost is None
|
||||
|
||||
def test_choosevhost_works(self):
|
||||
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
|
||||
@@ -1518,16 +1518,11 @@ class MultiVhostsTest(util.ApacheTest):
|
||||
with_index_1 = ["/path[1]/section[1]"]
|
||||
without_index = ["/path/section"]
|
||||
with_index_2 = ["/path[2]/section[2]"]
|
||||
assert self.config._get_new_vh_path(without_index,
|
||||
with_index_1) == \
|
||||
None
|
||||
assert self.config._get_new_vh_path(without_index,
|
||||
with_index_2) == \
|
||||
with_index_2[0]
|
||||
assert self.config._get_new_vh_path(without_index, with_index_1) is None
|
||||
assert self.config._get_new_vh_path(without_index, with_index_2) == with_index_2[0]
|
||||
|
||||
both = with_index_1 + with_index_2
|
||||
assert self.config._get_new_vh_path(without_index, both) == \
|
||||
with_index_2[0]
|
||||
assert self.config._get_new_vh_path(without_index, both) == with_index_2[0]
|
||||
|
||||
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
|
||||
def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_notify):
|
||||
@@ -1658,15 +1653,12 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
file has been manually edited by the user, and will refuse to update it.
|
||||
This test ensures that all necessary hashes are present.
|
||||
"""
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
import importlib.resources
|
||||
|
||||
from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES
|
||||
|
||||
ref = importlib_resources.files("certbot_apache") / "_internal" / "tls_configs"
|
||||
with importlib_resources.as_file(ref) as tls_configs_dir:
|
||||
ref = importlib.resources.files("certbot_apache") / "_internal" / "tls_configs"
|
||||
with importlib.resources.as_file(ref) as tls_configs_dir:
|
||||
all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir)
|
||||
if name.endswith('options-ssl-apache.conf')]
|
||||
assert len(all_files) >= 1
|
||||
@@ -1723,14 +1715,14 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
|
||||
self.config._openssl_version = None
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
assert self.config.openssl_version() == None
|
||||
assert self.config.openssl_version() is None
|
||||
assert "Could not find ssl_module" in mock_log.call_args[0][0]
|
||||
|
||||
# When no ssl_module is present at all
|
||||
self.config._openssl_version = None
|
||||
assert "ssl_module" not in self.config.parser.modules
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
assert self.config.openssl_version() == None
|
||||
assert self.config.openssl_version() is None
|
||||
assert "Could not find ssl_module" in mock_log.call_args[0][0]
|
||||
|
||||
# When ssl_module is statically linked but --apache-bin not provided
|
||||
@@ -1738,13 +1730,13 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
self.config.options.bin = None
|
||||
self.config.parser.modules['ssl_module'] = None
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
assert self.config.openssl_version() == None
|
||||
assert self.config.openssl_version() is None
|
||||
assert "ssl_module is statically linked but" in mock_log.call_args[0][0]
|
||||
|
||||
self.config.parser.modules['ssl_module'] = "/fake/path"
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
# Check that correct logger.warning was printed
|
||||
assert self.config.openssl_version() == None
|
||||
assert self.config.openssl_version() is None
|
||||
assert "Unable to read" in mock_log.call_args[0][0]
|
||||
|
||||
contents_missing_openssl = b"these contents won't match the regex"
|
||||
@@ -1753,7 +1745,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
mock_omf.return_value = contents_missing_openssl
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
# Check that correct logger.warning was printed
|
||||
assert self.config.openssl_version() == None
|
||||
assert self.config.openssl_version() is None
|
||||
assert "Could not find OpenSSL" in mock_log.call_args[0][0]
|
||||
|
||||
def test_open_module_file(self):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# We specify the minimum acme and certbot version as the current plugin
|
||||
@@ -9,9 +9,7 @@ install_requires = [
|
||||
# https://github.com/certbot/certbot/issues/8761 for more info.
|
||||
f'acme>={version}',
|
||||
f'certbot>={version}',
|
||||
'importlib_resources>=1.3.1; python_version < "3.9"',
|
||||
'python-augeas',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
dev_extras = [
|
||||
@@ -30,7 +28,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -39,7 +37,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
recursive-include certbot_integration_tests/assets *
|
||||
include certbot_integration_tests/py.typed
|
||||
include snap_integration_tests/py.typed
|
||||
include windows_installer_integration_tests/py.typed
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""This module contains advanced assertions for the certbot integration tests."""
|
||||
import io
|
||||
import os
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
@@ -60,7 +59,7 @@ def assert_hook_execution(probe_path: str, probe_content: str) -> None:
|
||||
:param str probe_content: content expected when the hook is executed
|
||||
"""
|
||||
encoding = 'utf-8' if POSIX_MODE else 'utf-16'
|
||||
with io.open(probe_path, 'rt', encoding=encoding) as file:
|
||||
with open(probe_path, 'rt', encoding=encoding) as file:
|
||||
data = file.read()
|
||||
|
||||
lines = [line.strip() for line in data.splitlines()]
|
||||
@@ -76,7 +75,7 @@ def assert_saved_lineage_option(config_dir: str, lineage: str,
|
||||
:param str option: the option key
|
||||
:param value: if desired, the expected option value
|
||||
"""
|
||||
with open(os.path.join(config_dir, 'renewal', '{0}.conf'.format(lineage))) as file_h:
|
||||
with open(os.path.join(config_dir, 'renewal', f'{lineage}.conf')) as file_h:
|
||||
assert f"{option} = {value if value else ''}" in file_h.read()
|
||||
|
||||
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
"""General purpose nginx test configuration generator."""
|
||||
import atexit
|
||||
import getpass
|
||||
import sys
|
||||
import importlib.resources
|
||||
from contextlib import ExitStack
|
||||
from typing import Optional
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
|
||||
def construct_nginx_config(nginx_root: str, nginx_webroot: str, http_port: int, https_port: int,
|
||||
other_port: int, default_server: bool, key_path: Optional[str] = None,
|
||||
@@ -32,16 +27,16 @@ def construct_nginx_config(nginx_root: str, nginx_webroot: str, http_port: int,
|
||||
if not key_path:
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
ref = (importlib_resources.files('certbot_integration_tests').joinpath('assets')
|
||||
ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
|
||||
.joinpath('key.pem'))
|
||||
key_path = str(file_manager.enter_context(importlib_resources.as_file(ref)))
|
||||
key_path = str(file_manager.enter_context(importlib.resources.as_file(ref)))
|
||||
|
||||
if not cert_path:
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
ref = (importlib_resources.files('certbot_integration_tests').joinpath('assets')
|
||||
ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
|
||||
.joinpath('cert.pem'))
|
||||
cert_path = str(file_manager.enter_context(importlib_resources.as_file(ref)))
|
||||
cert_path = str(file_manager.enter_context(importlib.resources.as_file(ref)))
|
||||
|
||||
return '''\
|
||||
# This error log will be written regardless of server scope error_log
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Module to handle the context of RFC2136 integration tests."""
|
||||
from contextlib import contextmanager
|
||||
import sys
|
||||
import importlib.resources
|
||||
import tempfile
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
@@ -11,11 +11,6 @@ import pytest
|
||||
from certbot_integration_tests.certbot_tests import context as certbot_context
|
||||
from certbot_integration_tests.utils import certbot_call
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
|
||||
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
|
||||
"""Integration test context for certbot-dns-rfc2136"""
|
||||
@@ -48,9 +43,9 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
|
||||
:yields: Path to credentials file
|
||||
:rtype: str
|
||||
"""
|
||||
src_ref_file = (importlib_resources.files('certbot_integration_tests').joinpath('assets')
|
||||
src_ref_file = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
|
||||
.joinpath('bind-config').joinpath(f'rfc2136-credentials-{label}.ini.tpl'))
|
||||
with importlib_resources.as_file(src_ref_file) as src_file:
|
||||
with importlib.resources.as_file(src_ref_file) as src_file:
|
||||
with open(src_file, 'r') as f:
|
||||
contents = f.read().format(
|
||||
server_address=self._dns_xdist['address'],
|
||||
|
||||
@@ -51,7 +51,7 @@ def _prepare_environ(workspace: str) -> Dict[str, str]:
|
||||
|
||||
# So, pytest is nice, and a little too nice for our usage.
|
||||
# In order to help user to call seamlessly any piece of python code without requiring to
|
||||
# install it as a full-fledged setuptools distribution for instance, it may inject the path
|
||||
# install it as a full-fledged Python package for instance, it may inject the path
|
||||
# to the test files into the PYTHONPATH. This allows the python interpreter to import
|
||||
# as modules any python file available at this path.
|
||||
# See https://docs.pytest.org/en/3.2.5/pythonpath.html for the explanation and description.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
"""Module to setup an RFC2136-capable DNS server"""
|
||||
import importlib.resources
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
@@ -17,11 +18,6 @@ from typing import Type
|
||||
|
||||
from certbot_integration_tests.utils import constants
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
BIND_DOCKER_IMAGE = "internetsystemsconsortium/bind9:9.20"
|
||||
BIND_BIND_ADDRESS = ("127.0.0.1", 45953)
|
||||
|
||||
@@ -83,8 +79,8 @@ class DNSServer:
|
||||
|
||||
def _configure_bind(self) -> None:
|
||||
"""Configure the BIND9 server based on the prebaked configuration"""
|
||||
ref = importlib_resources.files("certbot_integration_tests") / "assets" / "bind-config"
|
||||
with importlib_resources.as_file(ref) as path:
|
||||
ref = importlib.resources.files("certbot_integration_tests") / "assets" / "bind-config"
|
||||
with importlib.resources.as_file(ref) as path:
|
||||
for directory in ("conf", "zones"):
|
||||
shutil.copytree(
|
||||
os.path.join(path, directory), os.path.join(self.bind_root, directory)
|
||||
|
||||
@@ -7,6 +7,7 @@ import contextlib
|
||||
import errno
|
||||
import functools
|
||||
import http.server as SimpleHTTPServer
|
||||
import importlib.resources
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@@ -21,10 +22,13 @@ from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
import warnings
|
||||
from typing import Union
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.hazmat.primitives.serialization import NoEncryption
|
||||
from cryptography.hazmat.primitives.serialization import PrivateFormat
|
||||
@@ -36,11 +40,6 @@ import requests
|
||||
from certbot_integration_tests.utils.constants import PEBBLE_ALTERNATE_ROOTS
|
||||
from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
RSA_KEY_TYPE = 'rsa'
|
||||
ECDSA_KEY_TYPE = 'ecdsa'
|
||||
|
||||
@@ -124,9 +123,9 @@ def generate_test_file_hooks(config_dir: str, hook_probe: str) -> None:
|
||||
"""
|
||||
file_manager = contextlib.ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
hook_path_ref = (importlib_resources.files('certbot_integration_tests').joinpath('assets')
|
||||
hook_path_ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
|
||||
.joinpath('hook.py'))
|
||||
hook_path = str(file_manager.enter_context(importlib_resources.as_file(hook_path_ref)))
|
||||
hook_path = str(file_manager.enter_context(importlib.resources.as_file(hook_path_ref)))
|
||||
|
||||
for hook_dir in list_renewal_hooks_dirs(config_dir):
|
||||
# We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of
|
||||
@@ -199,8 +198,9 @@ shutil.rmtree(well_known)
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
|
||||
def generate_csr(domains: Iterable[str], key_path: str, csr_path: str,
|
||||
key_type: str = RSA_KEY_TYPE) -> None:
|
||||
def generate_csr(
|
||||
domains: Iterable[str], key_path: str, csr_path: str, key_type: str = RSA_KEY_TYPE
|
||||
) -> None:
|
||||
"""
|
||||
Generate a private key, and a CSR for the given domains using this key.
|
||||
:param domains: the domain names to include in the CSR
|
||||
@@ -209,35 +209,33 @@ def generate_csr(domains: Iterable[str], key_path: str, csr_path: str,
|
||||
:param str csr_path: path to the CSR that will be generated
|
||||
:param str key_type: type of the key (misc.RSA_KEY_TYPE or misc.ECDSA_KEY_TYPE)
|
||||
"""
|
||||
key: Union[rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey]
|
||||
if key_type == RSA_KEY_TYPE:
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, 2048)
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
elif key_type == ECDSA_KEY_TYPE:
|
||||
with warnings.catch_warnings():
|
||||
# Ignore a warning on some old versions of cryptography
|
||||
warnings.simplefilter('ignore', category=PendingDeprecationWarning)
|
||||
_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
|
||||
_bytes = _key.private_bytes(encoding=Encoding.PEM,
|
||||
format=PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=NoEncryption())
|
||||
key = crypto.load_privatekey(crypto.FILETYPE_PEM, _bytes)
|
||||
key = ec.generate_private_key(ec.SECP384R1())
|
||||
else:
|
||||
raise ValueError('Invalid key type: {0}'.format(key_type))
|
||||
raise ValueError("Invalid key type: {0}".format(key_type))
|
||||
|
||||
with open(key_path, 'wb') as file_h:
|
||||
file_h.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
|
||||
with open(key_path, "wb") as file_h:
|
||||
file_h.write(
|
||||
key.private_bytes(
|
||||
Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()
|
||||
)
|
||||
)
|
||||
|
||||
req = crypto.X509Req()
|
||||
san = ', '.join('DNS:{0}'.format(item) for item in domains)
|
||||
san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8'))
|
||||
req.add_extensions([san_constraint])
|
||||
csr = (
|
||||
x509.CertificateSigningRequestBuilder()
|
||||
.subject_name(x509.Name([]))
|
||||
.add_extension(
|
||||
x509.SubjectAlternativeName([x509.DNSName(d) for d in domains]),
|
||||
critical=False,
|
||||
)
|
||||
.sign(key, hashes.SHA256())
|
||||
)
|
||||
|
||||
req.set_pubkey(key)
|
||||
req.set_version(0)
|
||||
req.sign(key, 'sha256')
|
||||
|
||||
with open(csr_path, 'wb') as file_h:
|
||||
file_h.write(crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req))
|
||||
with open(csr_path, "wb") as file_h:
|
||||
file_h.write(csr.public_bytes(Encoding.DER))
|
||||
|
||||
|
||||
def read_certificate(cert_path: str) -> str:
|
||||
@@ -247,11 +245,13 @@ def read_certificate(cert_path: str) -> str:
|
||||
:param str cert_path: the path to the certificate
|
||||
:returns: the TEXT version of the certificate, as it would be displayed by openssl binary
|
||||
"""
|
||||
with open(cert_path, 'rb') as file:
|
||||
with open(cert_path, "rb") as file:
|
||||
data = file.read()
|
||||
|
||||
cert = crypto.load_certificate(crypto.FILETYPE_PEM, data)
|
||||
return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert).decode('utf-8')
|
||||
cert = x509.load_pem_x509_certificate(data)
|
||||
return crypto.dump_certificate(
|
||||
crypto.FILETYPE_TEXT, crypto.X509.from_cryptography(cert)
|
||||
).decode("utf-8")
|
||||
|
||||
|
||||
def load_sample_data_path(workspace: str) -> str:
|
||||
@@ -261,9 +261,9 @@ def load_sample_data_path(workspace: str) -> str:
|
||||
:returns: the path to the loaded sample data directory
|
||||
:rtype: str
|
||||
"""
|
||||
original_ref = (importlib_resources.files('certbot_integration_tests').joinpath('assets')
|
||||
original_ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
|
||||
.joinpath('sample-config'))
|
||||
with importlib_resources.as_file(original_ref) as original:
|
||||
with importlib.resources.as_file(original_ref) as original:
|
||||
copied = os.path.join(workspace, 'sample-config')
|
||||
shutil.copytree(original, copied, symlinks=True)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# pylint: disable=missing-module-docstring
|
||||
import atexit
|
||||
import importlib.resources
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import zipfile
|
||||
from contextlib import ExitStack
|
||||
from typing import Optional, Tuple
|
||||
@@ -14,11 +14,6 @@ import requests
|
||||
from certbot_integration_tests.utils.constants import DEFAULT_HTTP_01_PORT
|
||||
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
PEBBLE_VERSION = 'v2.5.1'
|
||||
|
||||
|
||||
@@ -26,8 +21,8 @@ def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> Tuple[str
|
||||
# pylint: disable=missing-function-docstring
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
pebble_path_ref = importlib_resources.files('certbot_integration_tests') / 'assets'
|
||||
assets_path = str(file_manager.enter_context(importlib_resources.as_file(pebble_path_ref)))
|
||||
pebble_path_ref = importlib.resources.files('certbot_integration_tests') / 'assets'
|
||||
assets_path = str(file_manager.enter_context(importlib.resources.as_file(pebble_path_ref)))
|
||||
|
||||
pebble_path = _fetch_asset('pebble', assets_path)
|
||||
challtestsrv_path = _fetch_asset('pebble-challtestsrv', assets_path)
|
||||
|
||||
@@ -6,7 +6,6 @@ version = '0.32.0.dev0'
|
||||
install_requires = [
|
||||
'coverage',
|
||||
'cryptography',
|
||||
'importlib_resources>=1.3.1; python_version < "3.9"',
|
||||
'pyopenssl',
|
||||
'pytest',
|
||||
'pytest-cov',
|
||||
@@ -22,7 +21,6 @@ install_requires = [
|
||||
# requests unvendored its dependencies in version 2.16.0 and this code relies on that for
|
||||
# calling `urllib3.disable_warnings`.
|
||||
'requests>=2.16.0',
|
||||
'setuptools',
|
||||
'types-python-dateutil',
|
||||
]
|
||||
|
||||
@@ -34,14 +32,13 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# type: ignore
|
||||
"""
|
||||
General conftest for pytest execution of all integration tests lying
|
||||
in the window_installer_integration tests package.
|
||||
As stated by pytest documentation, conftest module is used to set on
|
||||
for a directory a specific configuration using built-in pytest hooks.
|
||||
|
||||
See https://docs.pytest.org/en/latest/reference.html#hook-reference
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""
|
||||
Standard pytest hook to add options to the pytest parser.
|
||||
:param parser: current pytest parser that will be used on the CLI
|
||||
"""
|
||||
parser.addoption('--installer-path',
|
||||
default=os.path.join(ROOT_PATH, 'windows-installer', 'build',
|
||||
'nsis', 'certbot-beta-installer-win_amd64.exe'),
|
||||
help='set the path of the windows installer to use, default to '
|
||||
'CERTBOT_ROOT_PATH\\windows-installer\\build\\nsis\\certbot-beta-installer-win_amd64.exe') # pylint: disable=line-too-long
|
||||
parser.addoption('--allow-persistent-changes', action='store_true',
|
||||
help='needs to be set, and confirm that the test will make persistent changes on this machine') # pylint: disable=line-too-long
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""
|
||||
Standard pytest hook used to add a configuration logic for each node of a pytest run.
|
||||
:param config: the current pytest configuration
|
||||
"""
|
||||
if not config.option.allow_persistent_changes:
|
||||
raise RuntimeError('This integration test would install Certbot on your machine. '
|
||||
'Please run it again with the `--allow-persistent-changes` '
|
||||
'flag set to acknowledge.')
|
||||
@@ -1,69 +0,0 @@
|
||||
"""Module executing integration tests for the windows installer."""
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != 'nt', reason='Windows installer tests must be run on Windows.')
|
||||
def test_it(request: pytest.FixtureRequest) -> None:
|
||||
try:
|
||||
subprocess.check_call(['certbot', '--version'])
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
pass
|
||||
else:
|
||||
raise AssertionError('Expect certbot to not be available in the PATH.')
|
||||
|
||||
try:
|
||||
# Install certbot
|
||||
subprocess.check_call([request.config.option.installer_path, '/S'])
|
||||
|
||||
# Assert certbot is installed and runnable
|
||||
output = subprocess.check_output(['certbot', '--version'], universal_newlines=True)
|
||||
assert re.match(r'^certbot \d+\.\d+\.\d+.*$',
|
||||
output), 'Flag --version does not output a version.'
|
||||
|
||||
# Assert renew task is installed and ready
|
||||
output = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State',
|
||||
capture_stdout=True)
|
||||
assert output.strip() == 'Ready'
|
||||
|
||||
# Assert renew task is working
|
||||
now = time.time()
|
||||
_ps('Start-ScheduledTask -TaskName "Certbot Renew Task"')
|
||||
|
||||
status = 'Running'
|
||||
while status != 'Ready':
|
||||
status = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State',
|
||||
capture_stdout=True).strip()
|
||||
time.sleep(1)
|
||||
|
||||
log_path = os.path.join('C:\\', 'Certbot', 'log', 'letsencrypt.log')
|
||||
|
||||
modification_time = os.path.getmtime(log_path)
|
||||
assert now < modification_time, 'Certbot log file has not been modified by the renew task.'
|
||||
|
||||
with open(log_path) as file_h:
|
||||
data = file_h.read()
|
||||
assert 'no renewal failures' in data, 'Renew task did not execute properly.'
|
||||
|
||||
finally:
|
||||
# Sadly this command cannot work in non interactive mode: uninstaller will
|
||||
# ask explicitly permission in an UAC prompt
|
||||
# print('Uninstalling Certbot ...')
|
||||
# uninstall_path = _ps('(gci "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"' # pylint: disable=line-too-long
|
||||
# ' | foreach { gp $_.PSPath }'
|
||||
# ' | ? { $_ -match "Certbot" }'
|
||||
# ' | select UninstallString)'
|
||||
# '.UninstallString', capture_stdout=True)
|
||||
# subprocess.check_call([uninstall_path, '/S'])
|
||||
pass
|
||||
|
||||
|
||||
def _ps(powershell_str: str, capture_stdout: bool = False) -> Any:
|
||||
fn = subprocess.check_output if capture_stdout else subprocess.check_call
|
||||
return fn(['powershell.exe', '-c', powershell_str], # type: ignore[operator]
|
||||
universal_newlines=True)
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/python:3.8-buster
|
||||
FROM docker.io/python:3.11-buster
|
||||
LABEL maintainer="Brad Warren <bmw@eff.org>"
|
||||
|
||||
# This does not include the dependencies needed to build cryptography. See
|
||||
|
||||
@@ -18,7 +18,7 @@ from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
|
||||
from OpenSSL import crypto
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from urllib3.util import connection
|
||||
|
||||
from acme import challenges
|
||||
@@ -147,10 +147,10 @@ def test_installer(args: argparse.Namespace, plugin: common.Proxy, config: str,
|
||||
|
||||
def test_deploy_cert(plugin: common.Proxy, temp_dir: str, domains: List[str]) -> bool:
|
||||
"""Tests deploy_cert returning True if the tests are successful"""
|
||||
cert = crypto_util.gen_ss_cert(util.KEY, domains)
|
||||
cert = crypto_util.gen_ss_cert(util.KEY, domains).to_cryptography()
|
||||
cert_path = os.path.join(temp_dir, "cert.pem")
|
||||
with open(cert_path, "wb") as f:
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
||||
|
||||
for domain in domains:
|
||||
try:
|
||||
@@ -390,7 +390,7 @@ def _fake_dns_resolution(resolved_ip: str) -> Generator[None, None, None]:
|
||||
"""Monkey patch urllib3 to make any hostname be resolved to the provided IP"""
|
||||
_original_create_connection = connection.create_connection
|
||||
|
||||
def _patched_create_connection(address: Tuple[str, str],
|
||||
def _patched_create_connection(address: Tuple[str, int],
|
||||
*args: Any, **kwargs: Any) -> socket.socket:
|
||||
_, port = address
|
||||
return _original_create_connection((resolved_ip, port), *args, **kwargs)
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from OpenSSL import crypto
|
||||
from cryptography import x509
|
||||
import requests
|
||||
|
||||
from acme import crypto_util
|
||||
@@ -21,7 +21,7 @@ _VALIDATION_TIMEOUT = 10
|
||||
class Validator:
|
||||
"""Collection of functions to test a live webserver's configuration"""
|
||||
|
||||
def certificate(self, cert: crypto.X509, name: Union[str, bytes],
|
||||
def certificate(self, cert: x509.Certificate, name: Union[str, bytes],
|
||||
alt_host: Optional[str] = None, port: int = 443) -> bool:
|
||||
"""Verifies the certificate presented at name is cert"""
|
||||
if alt_host is None:
|
||||
@@ -39,7 +39,7 @@ class Validator:
|
||||
logger.exception(str(error))
|
||||
return False
|
||||
|
||||
return presented_cert.digest("sha256") == cert.digest("sha256")
|
||||
return presented_cert.to_cryptography() == cert
|
||||
|
||||
def redirect(self, name: str, port: int = 80,
|
||||
headers: Optional[Mapping[str, str]] = None) -> bool:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
@@ -18,14 +18,13 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# for now, do not upgrade to cloudflare>=2.20 to avoid deprecation warnings and the breaking
|
||||
# changes in version 3.0. see https://github.com/certbot/certbot/issues/9938
|
||||
'cloudflare>=1.5.1, <2.20',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -41,7 +40,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -50,7 +49,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'python-digitalocean>=1.11', # 1.15.0 or newer is recommended for TTL support
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# This version of lexicon is required to address the problem described in
|
||||
# https://github.com/AnalogJ/lexicon/issues/387.
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -41,7 +40,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -50,7 +49,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'google-api-python-client>=1.6.5',
|
||||
'google-auth>=2.16.0',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -40,7 +39,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -49,7 +48,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.15.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -51,7 +51,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
|
||||
self.auth.credentials.conf = lambda key: creds.get(key, None)
|
||||
client = self.orig_get_client()
|
||||
assert client.algorithm == self.auth.ALGORITHMS["HMAC-MD5"]
|
||||
assert client.sign_query == False
|
||||
assert client.sign_query is False
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_perform(self, unused_mock_get_utility):
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dnspython>=1.15.0',
|
||||
'setuptools>=41.6.0',
|
||||
# This version was chosen because it is the version packaged in RHEL 9 and Debian unstable. It
|
||||
# is possible this requirement could be relaxed to allow for an even older version of dnspython
|
||||
# if necessary.
|
||||
'dnspython>=2.6.1',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +41,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +50,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'boto3>=1.15.15',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -170,6 +170,6 @@ texinfo_documents = [
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
|
||||
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
|
||||
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
if os.environ.get('SNAP_BUILD'):
|
||||
@@ -39,7 +38,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -48,7 +47,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import atexit
|
||||
from contextlib import ExitStack
|
||||
import logging
|
||||
import importlib.resources
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Any
|
||||
@@ -40,11 +40,6 @@ from certbot_nginx._internal import nginxparser
|
||||
from certbot_nginx._internal import obj
|
||||
from certbot_nginx._internal import parser
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
NAME_RANK = 0
|
||||
START_WILDCARD_RANK = 1
|
||||
END_WILDCARD_RANK = 2
|
||||
@@ -171,10 +166,10 @@ class NginxConfigurator(common.Configurator):
|
||||
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
ref = (importlib_resources.files("certbot_nginx").joinpath("_internal")
|
||||
ref = (importlib.resources.files("certbot_nginx").joinpath("_internal")
|
||||
.joinpath("tls_configs").joinpath(config_filename))
|
||||
|
||||
return str(file_manager.enter_context(importlib_resources.as_file(ref)))
|
||||
return str(file_manager.enter_context(importlib.resources.as_file(ref)))
|
||||
|
||||
@property
|
||||
def mod_ssl_conf(self) -> str:
|
||||
@@ -691,7 +686,7 @@ class NginxConfigurator(common.Configurator):
|
||||
else:
|
||||
socket.inet_pton(socket.AF_INET, host)
|
||||
all_names.add(socket.gethostbyaddr(host)[0])
|
||||
except (socket.error, socket.herror, socket.timeout):
|
||||
except (OSError, socket.herror, socket.timeout):
|
||||
continue
|
||||
|
||||
return util.get_filtered_names(all_names)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""A class that performs HTTP-01 challenges for Nginx"""
|
||||
|
||||
import io
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import List
|
||||
@@ -139,7 +138,7 @@ class NginxHttp01(common.ChallengePerformer):
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
with io.open(self.challenge_conf, "w", encoding="utf-8") as new_conf:
|
||||
with open(self.challenge_conf, "w", encoding="utf-8") as new_conf:
|
||||
nginxparser.dump(config, new_conf)
|
||||
|
||||
def _default_listen_addresses(self) -> List[Addr]:
|
||||
|
||||
@@ -35,8 +35,8 @@ class RawNginxParser:
|
||||
"""A class that parses nginx configuration with pyparsing."""
|
||||
|
||||
# constants
|
||||
space = Optional(White()).leaveWhitespace()
|
||||
required_space = White().leaveWhitespace()
|
||||
space = Optional(White(ws=' \t\r\n\u00a0')).leaveWhitespace()
|
||||
required_space = White(ws=' \t\r\n\u00a0').leaveWhitespace()
|
||||
|
||||
left_bracket = Literal("{").suppress()
|
||||
right_bracket = space + Literal("}").suppress()
|
||||
@@ -102,8 +102,7 @@ class RawNginxDumper:
|
||||
if isinstance(item[0], list): # block
|
||||
yield "".join(item.pop(0)) + '{'
|
||||
for parameter in item.pop(0):
|
||||
for line in self.__iter__([parameter]): # negate "for b0 in blocks"
|
||||
yield line
|
||||
yield from self.__iter__([parameter]) # negate "for b0 in blocks"
|
||||
yield '}'
|
||||
else: # not a block - list of strings
|
||||
semicolon = ";"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import copy
|
||||
import functools
|
||||
import glob
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
@@ -211,11 +210,11 @@ class NginxParser:
|
||||
if item in self.parsed and not override:
|
||||
continue
|
||||
try:
|
||||
with io.open(item, "r", encoding="utf-8") as _file:
|
||||
with open(item, "r", encoding="utf-8") as _file:
|
||||
parsed = nginxparser.load(_file)
|
||||
self.parsed[item] = parsed
|
||||
trees.append(parsed)
|
||||
except IOError:
|
||||
except OSError:
|
||||
logger.warning("Could not open file: %s", item)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning("Could not read file: %s due to invalid "
|
||||
@@ -255,10 +254,10 @@ class NginxParser:
|
||||
continue
|
||||
out = nginxparser.dumps(tree)
|
||||
logger.debug('Writing nginx conf tree to %s:\n%s', filename, out)
|
||||
with io.open(filename, 'w', encoding='utf-8') as _file:
|
||||
with open(filename, 'w', encoding='utf-8') as _file:
|
||||
_file.write(out)
|
||||
|
||||
except IOError:
|
||||
except OSError:
|
||||
logger.error("Could not open file for writing: %s", filename)
|
||||
|
||||
def parse_server(self, server: UnspacedList) -> Dict[str, Any]:
|
||||
@@ -431,9 +430,9 @@ class NginxParser:
|
||||
def _parse_ssl_options(ssl_options: Optional[str]) -> List[UnspacedList]:
|
||||
if ssl_options is not None:
|
||||
try:
|
||||
with io.open(ssl_options, "r", encoding="utf-8") as _file:
|
||||
with open(ssl_options, "r", encoding="utf-8") as _file:
|
||||
return nginxparser.load(_file)
|
||||
except IOError:
|
||||
except OSError:
|
||||
logger.warning("Missing NGINX TLS options file: %s", ssl_options)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning("Could not read file: %s due to invalid character. "
|
||||
|
||||
@@ -185,8 +185,7 @@ class Statements(Parsable):
|
||||
match: Optional[Callable[["Parsable"], bool]] = None) -> Iterator[Any]:
|
||||
""" Combines each statement's iterator. """
|
||||
for elem in self._data:
|
||||
for sub_elem in elem.iterate(expanded, match):
|
||||
yield sub_elem
|
||||
yield from elem.iterate(expanded, match)
|
||||
|
||||
# ======== End overridden functions
|
||||
|
||||
@@ -310,8 +309,7 @@ class Block(Parsable):
|
||||
if match is None or match(self):
|
||||
yield self
|
||||
if expanded:
|
||||
for elem in self.contents.iterate(expanded, match):
|
||||
yield elem
|
||||
yield from self.contents.iterate(expanded, match)
|
||||
|
||||
def parse(self, raw_list: List[Any], add_spaces: bool = False) -> None:
|
||||
""" Parses a list that resembles a block.
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
import OpenSSL
|
||||
import pytest
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from acme import challenges
|
||||
from acme import messages
|
||||
from certbot import achallenges
|
||||
@@ -545,12 +547,10 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
cert, key = self.config._get_snakeoil_paths()
|
||||
assert os.path.exists(cert)
|
||||
assert os.path.exists(key)
|
||||
with open(cert) as cert_file:
|
||||
OpenSSL.crypto.load_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert_file.read())
|
||||
with open(key) as key_file:
|
||||
OpenSSL.crypto.load_privatekey(
|
||||
OpenSSL.crypto.FILETYPE_PEM, key_file.read())
|
||||
with open(cert, "rb") as cert_file:
|
||||
x509.load_pem_x509_certificate(cert_file.read())
|
||||
with open(key, "rb") as key_file:
|
||||
serialization.load_pem_private_key(key_file.read(), password=None)
|
||||
|
||||
def test_redirect_enhance(self):
|
||||
# Test that we successfully add a redirect when there is
|
||||
@@ -1074,16 +1074,13 @@ class InstallSslOptionsConfTest(util.NginxTest):
|
||||
file has been manually edited by the user, and will refuse to update it.
|
||||
This test ensures that all necessary hashes are present.
|
||||
"""
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
import importlib.resources
|
||||
|
||||
from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES
|
||||
|
||||
tls_configs_ref = importlib_resources.files("certbot_nginx").joinpath(
|
||||
tls_configs_ref = importlib.resources.files("certbot_nginx").joinpath(
|
||||
"_internal", "tls_configs")
|
||||
with importlib_resources.as_file(tls_configs_ref) as tls_configs_dir:
|
||||
with importlib.resources.as_file(tls_configs_ref) as tls_configs_dir:
|
||||
for tls_config_file in os.listdir(tls_configs_dir):
|
||||
file_hash = crypto_util.sha256sum(os.path.join(tls_configs_dir, tls_config_file))
|
||||
assert file_hash in ALL_SSL_OPTIONS_HASHES, \
|
||||
|
||||
@@ -362,6 +362,18 @@ class TestRawNginxParser(unittest.TestCase):
|
||||
parsed = loads("")
|
||||
assert parsed == []
|
||||
|
||||
def test_non_breaking_spaces(self):
|
||||
# non-breaking spaces
|
||||
test = u'\u00a0'
|
||||
loads(test)
|
||||
test = """
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
"""
|
||||
loads(test)
|
||||
|
||||
|
||||
class TestUnspacedList(unittest.TestCase):
|
||||
"""Test the UnspacedList data structure"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Common utilities for certbot_nginx."""
|
||||
import copy
|
||||
import importlib.resources
|
||||
import shutil
|
||||
import tempfile
|
||||
import sys
|
||||
@@ -15,11 +16,6 @@ from certbot.tests import util as test_util
|
||||
from certbot_nginx._internal import configurator
|
||||
from certbot_nginx._internal import nginxparser
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
class NginxTest(test_util.ConfigTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@@ -86,8 +82,8 @@ class NginxTest(test_util.ConfigTestCase):
|
||||
@contextmanager
|
||||
def get_data_filename(filename):
|
||||
"""Gets the filename of a test data file."""
|
||||
ref = importlib_resources.files(__package__) / "testdata" / "etc_nginx"/ filename
|
||||
with importlib_resources.as_file(ref) as path:
|
||||
ref = importlib.resources.files(__package__) / "testdata" / "etc_nginx"/ filename
|
||||
with importlib.resources.as_file(ref) as path:
|
||||
yield path
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.12.0.dev0'
|
||||
version = '3.2.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# We specify the minimum acme and certbot version as the current plugin
|
||||
@@ -9,11 +9,9 @@ install_requires = [
|
||||
# https://github.com/certbot/certbot/issues/8761 for more info.
|
||||
f'acme>={version}',
|
||||
f'certbot>={version}',
|
||||
'importlib_resources>=1.3.1; python_version < "3.9"',
|
||||
# pyOpenSSL 23.1.0 is a bad release: https://github.com/pyca/pyopenssl/issues/1199
|
||||
'PyOpenSSL>=17.5.0,!=23.1.0',
|
||||
'pyparsing>=2.2.1',
|
||||
'setuptools>=41.6.0',
|
||||
'pyparsing>=2.4.7',
|
||||
]
|
||||
|
||||
test_extras = [
|
||||
@@ -28,7 +26,7 @@ setup(
|
||||
author="Certbot Project",
|
||||
author_email='certbot-dev@eff.org',
|
||||
license='Apache License 2.0',
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
@@ -37,7 +35,6 @@ setup(
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -2,7 +2,56 @@
|
||||
|
||||
Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## 3.0.0 - master
|
||||
## 3.2.0 - main
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
|
||||
### Changed
|
||||
|
||||
* certbot-nginx now requires pyparsing>=2.4.7.
|
||||
*
|
||||
|
||||
### Fixed
|
||||
|
||||
* Allow nginx plugin to parse non-breaking spaces in nginx configuration files.
|
||||
*
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 3.1.0 - 2025-01-07
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
|
||||
### Changed
|
||||
|
||||
* Python 3.8 support was removed.
|
||||
* certbot-dns-rfc2136's minimum required version of dnspython is now 2.6.1.
|
||||
* Updated our Docker images to be based on Alpine Linux 3.20.
|
||||
* Our runtime dependency on setuptools has been dropped from all Certbot
|
||||
components.
|
||||
* Certbot's packages no longer depend on library importlib_resources.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Included an OpenSSL library that was missing in our Certbot snap fixing
|
||||
crashes affecting 32-bit ARM users.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 3.0.1 - 2024-11-14
|
||||
|
||||
### Fixed
|
||||
|
||||
* Removed a CryptographyDeprecationWarning that was being displayed to users
|
||||
when checking OCSP status.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 3.0.0 - 2024-11-05
|
||||
|
||||
### Added
|
||||
|
||||
@@ -18,6 +67,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
* The `certbot_dns_route53.authenticator` module has been removed. This should
|
||||
not affect any users of the plugin and instead would only affect developers
|
||||
trying to develop on top of the old code.
|
||||
* Support for Python 3.8 was deprecated and will be removed in our next planned
|
||||
release.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
|build-status|
|
||||
|
||||
.. |build-status| image:: https://img.shields.io/azure-devops/build/certbot/ba534f81-a483-4b9b-9b4e-a60bec8fee72/5/master
|
||||
.. |build-status| image:: https://img.shields.io/azure-devops/build/certbot/ba534f81-a483-4b9b-9b4e-a60bec8fee72/5/main
|
||||
:target: https://dev.azure.com/certbot/certbot/_build?definitionId=5
|
||||
:alt: Azure Pipelines CI status
|
||||
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/EFForg/design/master/logos/eff-certbot-lockup.png
|
||||
:width: 200
|
||||
:alt: EFF Certbot Logo
|
||||
@@ -39,7 +39,7 @@ Documentation: https://certbot.eff.org/docs
|
||||
|
||||
Software project: https://github.com/certbot/certbot
|
||||
|
||||
Changelog: https://github.com/certbot/certbot/blob/master/certbot/CHANGELOG.md
|
||||
Changelog: https://github.com/certbot/certbot/blob/main/certbot/CHANGELOG.md
|
||||
|
||||
For Contributors: https://certbot.eff.org/docs/contributing.html
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Certbot client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '2.12.0.dev0'
|
||||
__version__ = '3.2.0.dev0'
|
||||
|
||||
@@ -223,7 +223,7 @@ class AccountFileStorage(interfaces.AccountStorage):
|
||||
key = jose.JWK.json_loads(key_file.read())
|
||||
with open(self._metadata_path(account_dir_path)) as metadata_file:
|
||||
meta = Account.Meta.json_loads(metadata_file.read())
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
return Account(regr, key, meta)
|
||||
@@ -243,7 +243,7 @@ class AccountFileStorage(interfaces.AccountStorage):
|
||||
self._create(account, dir_path)
|
||||
self._update_meta(account, dir_path)
|
||||
self._update_regr(account, dir_path)
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
def update_regr(self, account: Account) -> None:
|
||||
@@ -255,7 +255,7 @@ class AccountFileStorage(interfaces.AccountStorage):
|
||||
try:
|
||||
dir_path = self._prepare(account)
|
||||
self._update_regr(account, dir_path)
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
def update_meta(self, account: Account) -> None:
|
||||
@@ -267,7 +267,7 @@ class AccountFileStorage(interfaces.AccountStorage):
|
||||
try:
|
||||
dir_path = self._prepare(account)
|
||||
self._update_meta(account, dir_path)
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
def delete(self, account_id: str) -> None:
|
||||
|
||||
@@ -117,7 +117,7 @@ def lineage_for_certname(cli_config: configuration.NamespaceConfig,
|
||||
return None
|
||||
try:
|
||||
return storage.RenewableCert(renewal_file, cli_config)
|
||||
except (errors.CertStorageError, IOError):
|
||||
except (OSError, errors.CertStorageError):
|
||||
logger.debug("Renewal conf file %s is broken.", renewal_file)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
return None
|
||||
@@ -419,7 +419,7 @@ def _search_lineages(cli_config: configuration.NamespaceConfig, func: Callable[.
|
||||
for renewal_file in storage.renewal_conf_files(cli_config):
|
||||
try:
|
||||
candidate_lineage = storage.RenewableCert(renewal_file, cli_config)
|
||||
except (errors.CertStorageError, IOError):
|
||||
except (OSError, errors.CertStorageError):
|
||||
logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
continue
|
||||
|
||||
@@ -40,7 +40,7 @@ def read_file(filename: str, mode: str = "rb") -> Tuple[str, Any]:
|
||||
with open(filename, mode) as the_file:
|
||||
contents = the_file.read()
|
||||
return filename, contents
|
||||
except IOError as exc:
|
||||
except OSError as exc:
|
||||
raise argparse.ArgumentTypeError(exc.strerror)
|
||||
|
||||
|
||||
|
||||
@@ -11,14 +11,15 @@ from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key
|
||||
import josepy as jose
|
||||
from josepy import ES256
|
||||
from josepy import ES384
|
||||
from josepy import ES512
|
||||
from josepy import RS256
|
||||
import OpenSSL
|
||||
|
||||
from acme import client as acme_client
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
@@ -804,12 +805,9 @@ def validate_key_csr(privkey: util.Key, csr: Optional[util.CSR] = None) -> None:
|
||||
|
||||
if csr:
|
||||
if csr.form == "der":
|
||||
csr_obj = OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, csr.data)
|
||||
cert_buffer = OpenSSL.crypto.dump_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_PEM, csr_obj
|
||||
)
|
||||
csr = util.CSR(csr.file, cert_buffer, "pem")
|
||||
csr_obj = x509.load_der_x509_csr(csr.data)
|
||||
csr_pem = csr_obj.public_bytes(serialization.Encoding.PEM)
|
||||
csr = util.CSR(csr.file, csr_pem, "pem")
|
||||
|
||||
# If CSR is provided, it must be readable and valid.
|
||||
if csr.data and not crypto_util.valid_csr(csr.data):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Certbot constants."""
|
||||
import atexit
|
||||
import importlib.resources
|
||||
import logging
|
||||
import sys
|
||||
from contextlib import ExitStack
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
@@ -10,11 +10,6 @@ from acme import challenges
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
|
||||
if sys.version_info >= (3, 9): # pragma: no cover
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins"
|
||||
"""Setuptools entry point group name for plugins."""
|
||||
|
||||
@@ -228,8 +223,8 @@ def _generate_ssl_dhparams_src_static() -> str:
|
||||
# Python process, and will be automatically cleaned up on exit.
|
||||
file_manager = ExitStack()
|
||||
atexit.register(file_manager.close)
|
||||
ssl_dhparams_src_ref = importlib_resources.files("certbot") / "ssl-dhparams.pem"
|
||||
return str(file_manager.enter_context(importlib_resources.as_file(ssl_dhparams_src_ref)))
|
||||
ssl_dhparams_src_ref = importlib.resources.files("certbot") / "ssl-dhparams.pem"
|
||||
return str(file_manager.enter_context(importlib.resources.as_file(ssl_dhparams_src_ref)))
|
||||
|
||||
SSL_DHPARAMS_SRC = _generate_ssl_dhparams_src_static()
|
||||
"""Path to the nginx ssl_dhparams file found in the Certbot distribution."""
|
||||
|
||||
@@ -124,7 +124,7 @@ class _UnixLockMechanism(_BaseLockMechanism):
|
||||
"""
|
||||
try:
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError as err:
|
||||
except OSError as err:
|
||||
if err.errno in (errno.EACCES, errno.EAGAIN):
|
||||
logger.debug('A lock on %s is held by another process.', self._path)
|
||||
raise errors.LockError('Another instance of Certbot is already running.')
|
||||
@@ -187,13 +187,13 @@ class _WindowsLockMechanism(_BaseLockMechanism):
|
||||
By default on Windows, acquiring a file handler gives exclusive access to the process
|
||||
and results in an effective lock. However, it is possible to explicitly acquire the
|
||||
file handler in shared access in terms of read and write, and this is done by os.open
|
||||
and io.open in Python. So an explicit lock needs to be done through the call of
|
||||
in Python. So an explicit lock needs to be done through the call of
|
||||
msvcrt.locking, that will lock the first byte of the file. In theory, it is also
|
||||
possible to access a file in shared delete access, allowing other processes to delete an
|
||||
opened file. But this needs also to be done explicitly by all processes using the Windows
|
||||
low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers
|
||||
state that deleting a file opened by a process from another process is not possible with
|
||||
os.open and io.open.
|
||||
os.open.
|
||||
Consequently, msvcrt.locking is sufficient to obtain an effective lock, and the race
|
||||
condition encountered on Linux is not possible on Windows, leading to a simpler workflow.
|
||||
"""
|
||||
@@ -210,7 +210,7 @@ class _WindowsLockMechanism(_BaseLockMechanism):
|
||||
# are only defined on Windows. See
|
||||
# https://github.com/python/typeshed/blob/16ae4c61201cd8b96b8b22cdfb2ab9e89ba5bcf2/stdlib/msvcrt.pyi.
|
||||
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) # type: ignore # pylint: disable=used-before-assignment
|
||||
except (IOError, OSError) as err:
|
||||
except OSError as err:
|
||||
if fd:
|
||||
os.close(fd)
|
||||
# Anything except EACCES is unexpected. Raise directly the error in that case.
|
||||
|
||||
@@ -167,7 +167,7 @@ def setup_log_file_handler(config: configuration.NamespaceConfig, logfile: str,
|
||||
handler = logging.handlers.RotatingFileHandler(
|
||||
log_file_path, maxBytes=2 ** 20,
|
||||
backupCount=config.max_log_backups)
|
||||
except IOError as error:
|
||||
except OSError as error:
|
||||
raise errors.Error(util.PERM_ERR_FMT.format(error))
|
||||
# rotate on each invocation, rollover only possible when maxBytes
|
||||
# is nonzero and backupCount is nonzero, so we set maxBytes as big
|
||||
|
||||
@@ -280,7 +280,8 @@ def _handle_identical_cert_request(config: configuration.NamespaceConfig,
|
||||
|
||||
if config.verb == "run":
|
||||
keep_opt = "Attempt to reinstall this existing certificate"
|
||||
elif config.verb == "certonly":
|
||||
else:
|
||||
assert config.verb == "certonly", "Unexpected Certbot subcommand"
|
||||
keep_opt = "Keep the existing certificate for now"
|
||||
choices = [keep_opt,
|
||||
"Renew & replace the certificate (may be subject to CA rate limits)"]
|
||||
|
||||
@@ -176,7 +176,12 @@ class PluginsRegistry(Mapping):
|
||||
|
||||
@classmethod
|
||||
def find_all(cls) -> 'PluginsRegistry':
|
||||
"""Find plugins using setuptools entry points."""
|
||||
"""Find plugins using Python package entry points.
|
||||
|
||||
See https://packaging.python.org/en/latest/specifications/entry-points/ for more info on
|
||||
entry points.
|
||||
|
||||
"""
|
||||
plugins: Dict[str, PluginEntryPoint] = {}
|
||||
plugin_paths_string = os.getenv('CERTBOT_PLUGIN_PATH')
|
||||
plugin_paths = plugin_paths_string.split(':') if plugin_paths_string else []
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import collections
|
||||
import errno
|
||||
import logging
|
||||
import socket
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import DefaultDict
|
||||
@@ -78,7 +77,7 @@ class ServerManager:
|
||||
try:
|
||||
servers = acme_standalone.HTTP01DualNetworkedServers(
|
||||
address, self.http_01_resources)
|
||||
except socket.error as error:
|
||||
except OSError as error:
|
||||
raise errors.StandaloneBindError(error, port)
|
||||
|
||||
servers.serve_forever()
|
||||
|
||||
@@ -74,7 +74,7 @@ def reconstitute(config: configuration.NamespaceConfig,
|
||||
"""
|
||||
try:
|
||||
renewal_candidate = storage.RenewableCert(full_path, config)
|
||||
except (errors.CertStorageError, IOError) as error:
|
||||
except (OSError, errors.CertStorageError) as error:
|
||||
logger.error("Renewal configuration file %s is broken.", full_path)
|
||||
logger.error("The error was: %s\nSkipping.", str(error))
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
@@ -568,8 +568,6 @@ def handle_renewal_request(config: configuration.NamespaceConfig) -> Tuple[list,
|
||||
raise errors.Error(
|
||||
f"{len(renew_failures)} renew failure(s), {len(parse_failures)} parse failure(s)")
|
||||
|
||||
# Windows installer integration tests rely on handle_renewal_request behavior here.
|
||||
# If the text below changes, these tests will need to be updated accordingly.
|
||||
logger.debug("no renewal failures")
|
||||
|
||||
return (renewed_domains, failed_domains)
|
||||
|
||||
@@ -33,6 +33,7 @@ _ARCH_TRIPLET_MAP = {
|
||||
'amd64': 'x86_64-linux-gnu',
|
||||
's390x': 's390x-linux-gnu',
|
||||
}
|
||||
CURRENT_PYTHON_VERSION_STRING = 'python3.12'
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -71,10 +72,35 @@ def prepare_env(cli_args: List[str]) -> List[str]:
|
||||
raise e
|
||||
|
||||
data = response.json()
|
||||
connections = ['/snap/{0}/current/lib/python3.12/site-packages/'.format(item['slot']['snap'])
|
||||
for item in data.get('result', {}).get('established', [])
|
||||
if item.get('plug', {}).get('plug') == 'plugin'
|
||||
and item.get('plug-attrs', {}).get('content') == 'certbot-1']
|
||||
connections = []
|
||||
outdated_plugins = []
|
||||
for plugin in data.get('result', {}).get('established', []):
|
||||
plug: str = plugin.get('plug', {}).get('plug')
|
||||
plug_content: str = plugin.get('plug-attrs', {}).get('content')
|
||||
if plug == 'plugin' and plug_content == 'certbot-1':
|
||||
plugin_name: str = plugin['slot']['snap']
|
||||
# First, check that the plugin is using our expected python version,
|
||||
# i.e. its "read" slot is something like
|
||||
# "$SNAP/lib/python3.12/site-packages". If not, skip it and print an
|
||||
# error.
|
||||
slot_read: str = plugin.get('slot-attrs', {}).get('read', [])
|
||||
if len(slot_read) != 0 and CURRENT_PYTHON_VERSION_STRING not in slot_read[0]:
|
||||
outdated_plugins.append(plugin_name)
|
||||
continue
|
||||
|
||||
connections.append('/snap/{0}/current/lib/{1}/site-packages/'.format(
|
||||
plugin_name,
|
||||
CURRENT_PYTHON_VERSION_STRING
|
||||
))
|
||||
|
||||
if outdated_plugins:
|
||||
LOGGER.warning('The following plugins are using an outdated python version and must be '
|
||||
'updated to be compatible with Certbot 3.0. Please see '
|
||||
'https://community.letsencrypt.org/t/'
|
||||
'certbot-3-0-could-have-potential-third-party-snap-breakages/226940 '
|
||||
'for more information:')
|
||||
plugin_list = '\n'.join(' * {}'.format(plugin) for plugin in outdated_plugins)
|
||||
LOGGER.warning(plugin_list)
|
||||
|
||||
os.environ['CERTBOT_PLUGIN_PATH'] = ':'.join(connections)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests for certbot._internal.account."""
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user