Compare commits

...

91 Commits

Author SHA1 Message Date
Erica Portnoy
d3a4a3c23a require new issues to use a templates 2025-01-16 14:45:38 -08:00
Alexis
86694397a6 Update notify_weekly.yaml (#10118)
Making the weekly message a little more useful.

---------

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2025-01-13 07:46:12 -08:00
ohemorange
b18c074088 Allow non-breaking spaces in nginx config files (#10126)
Fixes @josevavia's issue in #9942.
2025-01-10 15:25:05 -08:00
Brad Warren
f59a639ec4 improve repin experience on macOS (#10128)
this hopefully at least helps the problem hit at
https://github.com/certbot/certbot/pull/10126#discussion_r1909714276

i took this approach because in my experience, linux specific shell
commands have crept into our scripts repeatedly over the years so i
think just having macOS devs use the linux versions is much more
reliable. it's what i've personally been doing for years now
2025-01-10 12:54:54 -08:00
Brad Warren
5411e4c86a silence poetry warning (#10127)
when reviewing https://github.com/certbot/certbot/pull/10126 and running
`tools/pinning/oldest/repin.sh` using a freshly created dev environment,
i was repeatedly given the message

> The "poetry.dev-dependencies" section is deprecated and will be
removed in a future version. Use "poetry.group.dev.dependencies"
instead.

i believe this section was generated automatically by poetry's tooling
when it created the initial boilerplate file for us, but we don't use
it, so i just deleted the section which makes the warnings disappear
2025-01-10 12:52:24 -08:00
Brad Warren
0425b87b78 Merge pull request #10123 from certbot/candidate-3.1.0
Release Certbot 3.1.0
2025-01-07 15:53:09 -08:00
Erica Portnoy
1de966d637 Bump version to 3.2.0 2025-01-07 12:54:01 -08:00
Erica Portnoy
ba2e4aecb7 Add contents to certbot/CHANGELOG.md for next version 2025-01-07 12:54:01 -08:00
Erica Portnoy
7d2b1996d9 Remove built packages from git 2025-01-07 12:54:01 -08:00
Erica Portnoy
dcd52b0711 Release 3.1.0 2025-01-07 12:54:00 -08:00
Erica Portnoy
8074858620 Update changelog for 3.1.0 release 2025-01-07 12:53:36 -08:00
Brad Warren
d3d293299a minor acme doc & comment fixes (#10122)
this fixes two tiny things i noticed when reviewing
https://github.com/certbot/certbot/pull/10120

1. not all of our `acme` modules were generating API documentation
2. the deleted commend about a "type ignore" should have been deleted in
https://github.com/certbot/certbot/pull/9197 but will and i missed it
2025-01-07 18:17:00 +00:00
Alex Gaynor
9148acd332 Migrate verify_cert to take cryptography certificates (#10120) 2025-01-07 17:46:31 +00:00
Brad Warren
9f9a1df85e upgrade pylint (#10121)
we need this for https://github.com/certbot/certbot/issues/10045
2025-01-07 09:43:14 -08:00
ohemorange
985457e57b Add docstring for acme.crypto_util.get_names_from_subject_and_extensions (#10115)
It was my oversight to not request this when this function was made
public in https://github.com/certbot/certbot/pull/10111.
2025-01-06 14:37:29 -08:00
Alex Gaynor
4004589cbf Migrate certbot-compatibility-test to cryptography (as much as possible (#10117)
Also fixed a typing error.
2025-01-06 13:39:16 -08:00
Alex Gaynor
8f7c3756b3 Migrate get_serial_from_cert and valid_privkey to cryptography (#10116) 2025-01-06 13:34:57 -08:00
Alex Gaynor
6ea5da51e0 Simplify typing for a local variable (#10113)
`_DefaultCertSelection` _is_ a `Callable` of the appropriate signature.

Also fixed a mypy error I see locally, `TOKEN_SIZE` should be an
integer.
2025-01-06 13:18:28 -08:00
Alex Gaynor
1ac05ae891 Remove _pyopenssl_cert_or_req_san_ip which is unused, and migrate _pyopenssl_cert_or_req_all_names to cryptography (#10112)
Unfortunately the other helpers from this family are directly called by
(historic) versions of certbot, and so cannot be easily removed.
2025-01-06 12:46:23 -08:00
Manuel Baldassarri
a441debdaa Add Nginx Unit plugin to documentation (#10110)
## 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 `main` section of `certbot/CHANGELOG.md` to include a
description of the change being made.
- [x] Add or update any documentation as needed to support the changes
in this PR.
- [ ] Include your name in `AUTHORS.md` if you like.
2025-01-06 12:37:31 -08:00
Alex Gaynor
5dd898f56b Move _get_names_from_subject_and_extensions to acme's crypto_utils (#10111)
Make use of it in more places
2025-01-03 16:21:31 -08:00
Alex Gaynor
a1fce6b398 Convert notBefore and notAfter to use cryptography's APIs (#10103) 2025-01-03 13:50:33 -08:00
Will Greenberg
635d9c3ec3 Merge pull request #10090 from alex/san-cryptography
Convert several SAN handling functions to use cryptography's APIs
2025-01-02 11:42:49 -08:00
Alex Gaynor
0f36d0c1ba Convert several SAN handling functions to use cryptography's APIs 2025-01-02 14:25:17 -05:00
Alex Gaynor
619da0432a Introduce a Format enum to help us migrate away from pyOpenSSL's constants
Begin using it in `dump_pyopenssl_chain`
2024-12-21 11:06:43 -05:00
Alex Gaynor
314838eb81 Convert some certbot-ci utilities to use cryptography's APIs (#10102) 2024-12-19 19:37:09 +00:00
Will Greenberg
25a1933e01 snap: disable FIPS detection (#10067)
This is needed because the Python + OpenSSL bundled in core24 don't
include an OpenSSL FIPS provider, which causes crashes on host systems
with OpenSSL 1.1.1f (e.g. Ubuntu Pro 20.04). For some reason, core24's
OpenSSL also looks in a non-standard location for the provider, which
also causes crashes on systems with OpenSSL 3.x (e.g. RHEL 9). If you
need FIPS functionality in certbot, install via pip.
2024-12-19 10:55:53 -08:00
Alex Gaynor
0f500e8010 Convert crypto_util_test.py to use cryptography's APIs (#10100) 2024-12-19 10:24:16 -08:00
Alex Gaynor
1afae838bb Convert validate_key_csr to use cryptography's APIs (#10099) 2024-12-19 07:11:47 -08:00
Alex Gaynor
724be8848a Convert http01_example.py to use cryptography's APIs (#10098)
Co-authored-by: ohemorange <ebportnoy@gmail.com>
2024-12-18 23:54:44 +00:00
Alex Gaynor
06ea141ca9 Convert make_key to use cryptography's APIs (#10091) 2024-12-18 15:10:20 -08:00
Mads Jensen
23245c07b2 Replace assert False with pytest.fail (#10094)
This seems to be better style. The assert False statements are
automatically removed by Python when running in the optimized mode,
which could hide test failures.

## 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 `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.

Co-authored-by: Mads Jensen <atombrella@users.noreply.github.com>
2024-12-18 14:44:05 -08:00
Will Greenberg
2d1d1cd534 Merge pull request #10089 from jvanasco/fix-migrate_to_cryptography
switch `cert_and_chain_from_fullchain` to cryptography APIs
2024-12-17 14:37:02 -08:00
Will Greenberg
5240e3cbf2 Merge pull request #10085 from atombrella/pyupgrade/up020_open_alias
Replace io.open with the built-in.
2024-12-17 14:35:08 -08:00
Will Greenberg
5fca4a14ab Merge pull request #10084 from atombrella/pyupgrade/up024_oserror
Replace aliased OSError.
2024-12-17 14:34:10 -08:00
Alex Gaynor
9be070414f Convert valid_csr and csr_matches_pubkey to use cryptography's APIs (#10088) 2024-12-17 09:22:22 -08:00
jonathan vanasco
761c268934 missed import level in port 2024-12-16 15:42:33 -05:00
jonathan vanasco
1fa110c9d7 added to authors 2024-12-16 15:30:42 -05:00
jonathan vanasco
9d1fccf53a switch cert_and_chain_from_fullchain to cryptography 2024-12-16 15:24:38 -05:00
Alex Gaynor
b16c64a05b Convert make_csr to use cryptography instead of pyOpenSSL (#10086)
These pyOpenSSL APIs are deprecated and we'd like to remove them.
2024-12-16 11:00:52 -08:00
Mads Jensen
88932da859 lint 2024-12-14 11:33:59 +01:00
Mads Jensen
8a69b2f1d9 Replace io.open with the built-in.
As of Python 3, io.open is an alias for the built-in open function.
2024-12-14 11:29:40 +01:00
Mads Jensen
57b5942fc3 Replace aliased OSError.
As of Python 3.3, various errors were merged into OSError.
https://docs.python.org/3/library/exceptions.html#OSError
2024-12-14 11:15:26 +01:00
Brad Warren
0f0000298b improve repinning (#10082)
this PR hopefully improves two things that i hit while working on #10035

1) i found that repinning our dependencies took ~6 minutes!

digging into it a bit, the biggest culprit i found was the inclusion of
`--no-cache-dir` here which seemed to cause poetry to redownload the
same packages over and over in a single run. this comes from
https://github.com/certbot/certbot/pull/9453 which fixed a problem i
(but not alex) was having with a major performance penalty. i removed
the flag here and instead included instructions on clearing poetry's
caches in case anyone ever hits this in the future. with this change,
the script now takes about 40 seconds on my laptop

2) every run of this script ended with the output:

    ```
Warning: poetry-plugin-export will not be installed by default in a
future version of Poetry.
In order to avoid a breaking change and make your automation
forward-compatible, please install poetry-plugin-export.
explicitly. See https://python-poetry.org/docs/plugins/#using-plugins
for details on how to install a plugin.
To disable this warning run 'poetry config warnings.export false'.
    ```

setting `POETRY_WARNINGS_EXPORT=false` fixes this which i believe is
safe to do because of
2c8609464c/certbot/setup.py (L53-L56)
2024-12-12 12:00:11 -08:00
Will Greenberg
c39fbe388c Merge pull request #10081 from certbot/no-windows-installer
remove the windows installer
2024-12-12 10:40:44 -08:00
Brad Warren
fc07f5f935 update pinnings 2024-12-12 08:57:10 -08:00
Brad Warren
9c8cdd05da remove the windows installer 2024-12-12 08:57:10 -08:00
Brad Warren
2c8609464c fix upgrading pyopenssl (#10080)
i hit this when working on https://github.com/certbot/certbot/pull/10076
where i found that updating all our dependencies no longer worked
because of new deprecations in pyopenssl. this pr fixes that
2024-12-11 15:15:55 -08:00
Brad Warren
7a48c235a9 remove importlib_resources (#10076)
this is part of my work on
https://github.com/certbot/certbot/issues/10035 based on erica's comment
at
https://github.com/certbot/certbot/issues/10035#issuecomment-2452212686
2024-12-06 12:37:17 -08:00
Mads Jensen
3f9387bd15 Fix F541 and E711 (#10071)
There are a quite a lot of imports that are unused.

F541 is Unnecessary f-interpolation without placeholders
E711 is incorrect use of == for boolean and None comparisons

## Pull Request Checklist

- [x] 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 `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.
- [x] Include your name in `AUTHORS.md` if you like.

---------

Co-authored-by: Mads Jensen <atombrella@users.noreply.github.com>
2024-12-05 11:33:09 -08:00
Brad Warren
087cb4d1f4 remove python 3.8 support (#10077)
fixes https://github.com/certbot/certbot/issues/10035. you can compare
this to the PR that did this for python 3.7 at
https://github.com/certbot/certbot/pull/9792

i agree with erica's comment at
https://github.com/certbot/certbot/issues/10035#issuecomment-2452212686,
but felt this PR was already getting pretty large so i did that in a
second PR at https://github.com/certbot/certbot/pull/10076
2024-12-04 14:55:20 -08:00
Brad Warren
bcbc3dd484 Merge pull request #10075 from certbot/test-no-setuptools
remove setuptools dependency
2024-12-03 13:58:03 -08:00
Brad Warren
89737718c1 update documentation and pinnings 2024-12-03 11:25:02 -08:00
Harlan Lieberman-Berg
b0e389aad7 Drop setuptools as a runtime dependency
Because of the change from using setuptools.pkg_resources to using
importlib, we no longer need a runtime dependency on setuptools. It is
still required, however, for running setup.py.
2024-12-03 11:17:27 -08:00
Brad Warren
9f5451d16b update intersphinx mapping (#10074)
this hopefully fixes our nightly failures

readthedocs seems to redirect users to its .io site so
https://acme-python.readthedocs.org/en/latest/objects.inv is supposed to
redirect people to
https://acme-python.readthedocs.io/en/latest/objects.inv, but that
doesn't always seem to work and instead [sometimes serves a
403](https://dev.azure.com/certbot/certbot/_build/results?buildId=8237&view=logs&j=d74e04fe-9740-597d-e9fa-1d0400037dfd&t=dde413a4-f24c-59a0-9684-e33d79f9aa02&l=800)

removing the need for this redirect seems to fix things based on some
quick testing and certainly shouldn't hurt
2024-12-03 11:16:13 -08:00
Will Greenberg
5ada20cb74 Merge pull request #10068 from certbot/test-include-ssl-provider
stage SSL lib and set OPENSSL_MODULES
2024-11-22 13:22:09 -08:00
Brad Warren
ba256adcdb add changelog entry 2024-11-22 11:23:00 -08:00
Brad Warren
94adff7247 stage ssl lib and set OPENSSL_MODULES 2024-11-22 11:00:56 -08:00
Will Greenberg
06d6231d6d Merge pull request #10060 from certbot/candidate-3.0.1
update main from 3.0.1 release
2024-11-18 13:38:23 -08:00
Brad Warren
61da18cc47 Merge branch 'main' into candidate-3.0.1 2024-11-18 13:25:09 -08:00
Brad Warren
7ab421233e remove old python macos cover tests (#10063)
Co-authored-by: Will Greenberg <willg@eff.org>
2024-11-18 19:18:46 +00:00
Brad Warren
59f32c9d11 update docker image (#10057) 2024-11-14 13:37:08 -08:00
Brad Warren
2b57c5f03c Bump version to 3.1.0 2024-11-14 09:22:05 -08:00
Brad Warren
8f75af1e84 Add contents to certbot/CHANGELOG.md for next version 2024-11-14 09:22:05 -08:00
Brad Warren
3c9b936168 Remove built packages from git 2024-11-14 09:22:04 -08:00
Brad Warren
93294fc989 Release 3.0.1 2024-11-14 09:22:03 -08:00
Brad Warren
58a07ddd79 Update changelog for 3.0.1 release 2024-11-14 09:21:24 -08:00
Will Greenberg
933c1703b6 Remove built packages from git
(cherry picked from commit 990352e371)
2024-11-14 09:21:04 -08:00
Brad Warren
a44d739dd7 silence warning and add test (#10054) (#10058)
fixes https://github.com/certbot/certbot/issues/9967

actually fixing the underlying issue is being tracked by https://github.com/certbot/certbot/issues/10053

in addition to the unit test, i tested this manually. if you obtain any cert and run `certbot renew` in an up-to-date dev environment, there's 3 warnings when on `main` and none on this branch

(cherry picked from commit aa6ea3b513)
2024-11-14 09:07:06 -08:00
Brad Warren
58374867c8 Merge pull request #10056 from certbot/prep-for-3.0.1
prep for 3.0.1
2024-11-13 20:40:33 -08:00
Brad Warren
aa6ea3b513 silence warning and add test (#10054)
fixes https://github.com/certbot/certbot/issues/9967

actually fixing the underlying issue is being tracked by https://github.com/certbot/certbot/issues/10053

in addition to the unit test, i tested this manually. if you obtain any cert and run `certbot renew` in an up-to-date dev environment, there's 3 warnings when on `main` and none on this branch
2024-11-13 20:39:50 -08:00
ohemorange
a25ef72c4f escape backslashes in format string in finish_release.py (#10043)
(cherry picked from commit 38fc7fcc48)
2024-11-13 10:34:32 -08:00
ohemorange
396b6cce02 Fix release script main replacement (#10042)
* restore incorrect regex changes to CHANGELOG.md

* Update _release.sh regex to switch only first instance of main in changelog

(cherry picked from commit 0e225dcba2)
2024-11-13 10:34:26 -08:00
ohemorange
38fc7fcc48 escape backslashes in format string in finish_release.py (#10043) 2024-11-05 23:42:59 +00:00
ohemorange
0e225dcba2 Fix release script main replacement (#10042)
* restore incorrect regex changes to CHANGELOG.md

* Update _release.sh regex to switch only first instance of main in changelog
2024-11-05 14:55:23 -08:00
Brad Warren
4ff5719a65 Merge pull request #10039 from certbot/candidate-3.0.0
Candidate 3.0.0
2024-11-05 12:52:47 -08:00
Will Greenberg
798a61622c Bump version to 3.1.0 2024-11-05 10:55:20 -08:00
Will Greenberg
b20d01e032 Add contents to certbot/CHANGELOG.md for next version 2024-11-05 10:55:20 -08:00
Will Greenberg
990352e371 Remove built packages from git 2024-11-05 10:55:20 -08:00
Will Greenberg
c5a5d6f9a1 Release 3.0.0 2024-11-05 10:55:19 -08:00
Will Greenberg
d4850399c5 Update changelog for 3.0.0 release 2024-11-05 10:54:15 -08:00
Brad Warren
c4be440853 update dependencies (#10036)
this fixes the current [dependabot alert](https://github.com/certbot/certbot/security/dependabot)
2024-11-01 10:04:10 -07:00
Will Greenberg
165c3e32b0 snap: fix generated postrefreshhook script (#9994)
Fixes #9990

If the python oneliner to check certbot's version succeeded, exit_code
would never be set, which would cause our exit_code check to fail. Use
a check that handles unset exit_code
2024-11-01 08:03:57 -07:00
Will Greenberg
2660a2017b Certbot 3.0 outdated plugin warning (#10031)
* Print an error if outdated snap plugins detected

With Certbot 3.0 comes a bump to Python 3.12, so if any snap plugins
are still located in a python3.8 directory, print an error informing
the user.

* tox nitpicks

* personal nitpick

* review fixups

* Update certbot/certbot/_internal/snap_config.py

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* Use LOGGER.warn instead of error

* warn-->warning

* warn-->warning

---------

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2024-11-01 07:52:48 -07:00
ohemorange
6a6544fd90 Update azure standard tests to use macOS-15 and python3.12 (#10032)
macOS-12 is [being deprecated](https://github.com/actions/runner-images/issues/10721) on Azure, so update to the latest available version.

* Upgrade macOS azure tests to use macOS-15

* switch standard azure tests to using python 3.12

* restore mac and linux cover tests to oldest and newest version style, and add explanation that that's what we're doing.
2024-11-01 07:34:16 -07:00
Brad Warren
320cf92944 depecate py38 support (#10034) 2024-10-31 15:48:57 -07:00
Brad Warren
3078c2f3db remove reference to "good first issue" label (#10018) 2024-10-25 11:43:44 -07:00
Brad Warren
c54f99e35b mattermost/action-mattermost-notify still uses master (#10021) 2024-10-04 14:08:25 -07:00
Brad Warren
c81dbb2582 Make Docker builds more verbose (#10022)
* use consistent casing to fix warnings

* don't truncate docker build logs

* make docker build output verbose
2024-10-04 13:54:56 -07:00
Will Greenberg
742f97e11a docs: fix logo url (#10019) 2024-09-26 15:10:06 -07:00
Will Greenberg
84c8dbc52a Migrate master branch to main
We're a few years behind the curve on this one, but using "master" as a
programming term is a callous practice that explicitly uses the
historical institution of slavery as a cheap, racist metaphor. Switch to
using "main", as it's the new default in git and GitHub.
2024-09-26 14:48:10 -07:00
160 changed files with 1268 additions and 1905 deletions

View File

@@ -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`._

View File

@@ -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:

View File

@@ -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:

View File

@@ -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)

View File

@@ -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
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -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.

View File

@@ -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 }}

View File

@@ -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 }})

View File

@@ -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)

View File

@@ -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()

View File

@@ -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):

View File

@@ -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

View File

@@ -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(

View File

@@ -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()

View File

@@ -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-----

View File

@@ -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,

View File

@@ -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())

View File

@@ -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

View File

@@ -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"""

View File

@@ -0,0 +1,5 @@
Crypto_util
-----------
.. automodule:: acme.crypto_util
:members:

5
acme/docs/api/jws.rst Normal file
View File

@@ -0,0 +1,5 @@
JWS
---
.. automodule:: acme.jws
:members:

5
acme/docs/api/util.rst Normal file
View File

@@ -0,0 +1,5 @@
Util
----
.. automodule:: acme.util
:members:

View File

@@ -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

View File

@@ -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',

View File

@@ -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)))

View File

@@ -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")

View File

@@ -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"""

View File

@@ -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

View File

@@ -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):

View File

@@ -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',

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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'],

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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',

View File

@@ -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.')

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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):

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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),
}

View File

@@ -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',

View File

@@ -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)

View File

@@ -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]:

View File

@@ -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 = ";"

View File

@@ -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. "

View File

@@ -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.

View File

@@ -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, \

View File

@@ -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"""

View File

@@ -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

View File

@@ -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',

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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."""

View File

@@ -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.

View File

@@ -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

View File

@@ -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)"]

View File

@@ -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 []

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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