Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-service_identity for
openSUSE:Factory checked in at 2026-07-07 20:59:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-service_identity (Old)
and /work/SRC/openSUSE:Factory/.python-service_identity.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-service_identity"
Tue Jul 7 20:59:49 2026 rev:17 rq:1363849 version:26.1.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-service_identity/python-service_identity.changes
2024-11-01 21:00:48.745507595 +0100
+++
/work/SRC/openSUSE:Factory/.python-service_identity.new.1982/python-service_identity.changes
2026-07-07 21:00:05.471881984 +0200
@@ -1,0 +2,15 @@
+Sun Jul 5 10:41:02 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 26.1.0:
+ * Python 3.14 and 3.15 are now officially supported.
+ * *service-identity* now uses *cryptography*'s Rust-based ASN.1
+ decoder and doesn't depend on *pyasn1* and *pyasn1-modules*
+ anymore.
+ * As a result, the oldest supported pyOpenSSL backend
+ combination is now *pyOpenSSL* 26.1.0 with *cryptography*
+ 47.0.0.
+ * Verifying a single-label hostname (e.g. `localhost`) against
+ a wildcard certificate pattern now raises `VerificationError`
+ cleanly instead of crashing with an opaque `ValueError`.
+
+-------------------------------------------------------------------
Old:
----
24.2.0.tar.gz
New:
----
26.1.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-service_identity.spec ++++++
--- /var/tmp/diff_new_pack.pMh3ey/_old 2026-07-07 21:00:06.359911999 +0200
+++ /var/tmp/diff_new_pack.pMh3ey/_new 2026-07-07 21:00:06.363912135 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-service_identity
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
%define oname service_identity
%{?sle15_python_module_pythons}
Name: python-service_identity
-Version: 24.2.0
+Version: 26.1.0
Release: 0
Summary: Service identity verification for pyOpenSSL
License: MIT
++++++ 24.2.0.tar.gz -> 26.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.git_archival.txt
new/service-identity-26.1.0/.git_archival.txt
--- old/service-identity-24.2.0/.git_archival.txt 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.git_archival.txt 2026-05-30
13:50:32.000000000 +0200
@@ -1,3 +1,3 @@
-node: b51873e5aff777ff2e35dfb2bee1f4acbf203b25
-node-date: 2024-10-26T09:10:38+02:00
-describe-name: 24.2.0
+node: 5025f92e3856e814bb5012ba4ed03b71320131d8
+node-date: 2026-05-30T13:50:32+02:00
+describe-name: 26.1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/AI_POLICY.md
new/service-identity-26.1.0/.github/AI_POLICY.md
--- old/service-identity-24.2.0/.github/AI_POLICY.md 1970-01-01
01:00:00.000000000 +0100
+++ new/service-identity-26.1.0/.github/AI_POLICY.md 2026-05-30
13:50:32.000000000 +0200
@@ -0,0 +1,71 @@
+# Generative AI / LLM Policy
+
+We appreciate that we can't realistically police how you author your pull
requests, which includes whether you employ large-language model (LLM)-based
development tools.
+So, we don't.
+
+However, due to both legal and human reasons, we have to establish boundaries.
+
+> [!CAUTION]
+> **TL;DR:**
+> - We take the responsibility for this project very seriously and we expect
you to take your responsibility for your contributions seriously, too.
+> This used to be a given, but it changed now that a pull request is just
one prompt away.
+>
+> - Every contribution has to be backed by a human who unequivocally owns the
copyright for all changes.
+> No LLM bots in `Co-authored-by:`s.
+>
+> - DoS-by-slop leads to a permanent ban.
+>
+> - Absolutely **no** unsupervised agentic tools like OpenClaw.
+>
+> ---
+>
+> By submitting a pull request, you certify that:
+>
+> - You are the author of the contribution or have the legal right to submit
it.
+> - You either hold the copyright to the changes or have explicit legal
authorization to contribute them under this project's license.
+> - You understand the code.
+> - You accept full responsibility for it.
+
+
+## Legal
+
+There is ongoing legal uncertainty regarding the copyright status of
LLM-generated works and their provenance.
+Since we do not have a formal [Contributor License
Agreement](https://en.wikipedia.org/wiki/Contributor_license_agreement) (CLA),
you retain your copyright to your changes to this project.
+
+Therefore, allowing contributions by LLMs has unpredictable consequences for
the copyright status of this project – even when leaving aside possible
copyright violations due to plagiarism.
+
+
+## Human
+
+As the makers of software that is used by millions of people worldwide and
with a reputation for high-quality maintenance, we take our responsibility to
our users very seriously.
+No matter what LLM vendors or boosters on LinkedIn tell you, we have to
manually review every change before merging, because it's **our
responsibility** to keep the project stable.
+
+Please understand that by opening low-quality pull requests you're not helping
anyone.
+Worse, you're [poisoning the open source
ecosystem](https://lwn.net/Articles/1058266/) that was precarious even before
the arrival of LLM tools.
+Having to wade through plausible-looking-but-low-quality pull requests and
trying to determine which ones are legit is extremely demoralizing and has
already burned out many good maintainers.
+
+Put bluntly, we have no time or interest to become part of your vibe coding
loop where you drop LLM slop at our door, we spend time and energy to review
it, and you just feed it back into the LLM for another iteration.
+
+This dynamic is especially pernicious because it poisons the well for
mentoring new contributors which we are committed to.
+
+
+## Summary
+
+In practice, this means:
+
+- Pull requests that have an LLM product listed as co-author can't be merged
and will be closed without further discussion.
+ We cannot risk the copyright status of this project.
+
+ If you used LLM tools during development, you may still submit – but you
must remove any LLM co-author tags and take full ownership of every line.
+
+- By submitting a pull request, **you** take full **technical and legal**
responsibility for the contents of the pull request and promise that **you**
hold the copyright for the changes submitted.
+
+ "An LLM wrote it" is **not** an acceptable response to questions or critique.
+ **If you cannot explain and defend the changes you submit, do not submit
them** and open a high-quality bug report/feature request instead.
+
+- Accounts that exercise bot-like behavior – like automated mass pull requests
– will be permanently banned, whether they belong to a human or not.
+
+- Do **not** post LLM-generated review comments – we can prompt LLMs ourselves
should we desire their wisdom.
+ Do **not** post summaries unless you've fact-checked them and take
responsibility for 100% of their content.
+ Remember that *all* LLM output *looks* **plausible**.
+ When using these tools, it's **your** responsibility to ensure that it's
also **correct** and has a reasonable signal-to-noise ratio.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/CODE_OF_CONDUCT.md
new/service-identity-26.1.0/.github/CODE_OF_CONDUCT.md
--- old/service-identity-24.2.0/.github/CODE_OF_CONDUCT.md 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/CODE_OF_CONDUCT.md 2026-05-30
13:50:32.000000000 +0200
@@ -1,48 +1,16 @@
-# Contributor Covenant Code of Conduct
+# Code of Conduct
-## Our Pledge
+While not being a [Python Software
Foundation](https://www.python.org/psf-landing/) project, everyone interacting
in this project is expected to follow the [PSF Code of
Conduct](https://policies.python.org/python.org/code-of-conduct/).
-In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age,
body size, disability, ethnicity, gender identity and expression, level of
experience, nationality, personal appearance, race, religion, or sexual
identity and orientation.
+In general, this means that everyone is expected to be **open**,
**considerate**, and **respectful** of others no matter what their position is
within the project.
-## Our Standards
-
-Examples of behavior that contributes to creating a positive environment
include:
-
-- Using welcoming and inclusive language
-- Being respectful of differing viewpoints and experiences
-- Gracefully accepting constructive criticism
-- Focusing on what is best for the community
-- Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-- The use of sexualized language or imagery and unwelcome sexual attention or
advances
-- Trolling, insulting/derogatory comments, and personal or political attacks
-- Public or private harassment
-- Publishing others' private information, such as a physical or electronic
address, without explicit permission
-- Other conduct which could reasonably be considered inappropriate in a
professional setting
-
-## Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
-
-## Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
-Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event.
-Representation of a project may be further defined and clarified by project
maintainers.
## Enforcement
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <mailto:[email protected]>.
-All complaints will be reviewed and investigated and will result in a response
that is deemed necessary and appropriate to the circumstances.
-The project team is obligated to maintain confidentiality with regard to the
reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
+We take Code of Conduct violations seriously, and will act to ensure our
spaces are welcoming, inclusive, and professional environments to communicate
in.
-Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
+If you need to raise a Code of Conduct report, you may do so privately by
email to [Hynek Schlawack](mailto:[email protected]).
-## Attribution
+Reports will be treated confidentially.
-This Code of Conduct is adapted from the [Contributor
Covenant](https://www.contributor-covenant.org), version 1.4, available at
\<<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>>.
+Alternately you can make a [report to the Python Software
Foundation](https://policies.python.org/python.org/code-of-conduct/Procedures-for-Reporting-Incidents/).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/CONTRIBUTING.md
new/service-identity-26.1.0/.github/CONTRIBUTING.md
--- old/service-identity-24.2.0/.github/CONTRIBUTING.md 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/CONTRIBUTING.md 2026-05-30
13:50:32.000000000 +0200
@@ -1,10 +1,10 @@
# How To Contribute
-Thank you for considering contributing to *service-identity*!
-It's people like *you* who make it such a great tool for everyone.
-
-This document intends to make contribution more accessible by codifying tribal
knowledge and expectations.
-Don't be afraid to open half-finished PRs, and ask questions if something is
unclear!
+> [!IMPORTANT]
+> - This document is mainly to help you to get started by codifying tribal
knowledge and expectations and make it more accessible to everyone.
+> But don't be afraid to open half-finished PRs and ask questions if
something is unclear!
+>
+> - If you use LLM / "AI" tools for your contributions, please read and follow
our [_Generative AI / LLM Policy_][llm].
Please note that this project is released with a Contributor [Code of
Conduct](https://github.com/pyca/service-identity/blob/main/.github/CODE_OF_CONDUCT.md).
By participating in this project you agree to abide by its terms.
@@ -13,67 +13,82 @@
## Workflow
+Thank you for considering contributing to *service-identity*!
+It's people like *you* who make it such a great tool for everyone.
+
+- **Only contribute code that you fully understand.**
+ See also our [AI policy][llm].
+
- No contribution is too small!
Please submit as many fixes for typos and grammar bloopers as you can!
+
+- Before starting big contributions, **talk to us first**.
+ Don't waste energy / tokens on something that we do not want.
+ Rejecting a huge PR is unpleasant for everybody.
+
- Try to limit each pull request to *one* change only.
+
- Since we squash on merge, it's up to you how you handle updates to the
`main` branch.
Whether you prefer to rebase on `main` or merge `main` into your branch, do
whatever is more comfortable for you.
+
+ Just remember to [not use your own `main` branch for the pull
request](https://hynek.me/articles/pull-requests-branch/).
+
- *Always* add tests and docs for your code.
This is a hard rule; patches with missing tests or documentation won't be
merged.
+
- Make sure your changes pass our [CI].
You won't get any feedback until it's green unless you ask for it.
+
For the CI to pass, the coverage must be 100%.
If you have problems to test something, open anyway and ask for advice.
In some situations, we may agree to add an `# pragma: no cover`.
+
- Once you've addressed review feedback, make sure to bump the pull request
with a short note, so we know you're done.
+
- Don’t break backwards-compatibility.
## Local Development Environment
-You can (and should) run our test suite using [*tox*].
-However, you’ll probably want a more traditional environment as well.
+First, **fork** the repository on GitHub.
+Make sure to **uncheck** the `Copy the main branch only` radio button on the
`Create a new fork` page.
+If you don't, our test suite will fail because we use Git tags for packaging.
-First, create a virtual environment so you don't break your system-wide Python
installation.
-We recommend using the Python version from the `.python-version-default` file
in project's root directory.
+Finally, **clone** it using one of the alternatives that you can copy-paste by
pressing the big green button labeled `<> Code`.
-If you're using [*direnv*](https://direnv.net), you can automate the creation
of a virtual environment with the correct Python version by adding the
following `.envrc` to the project root after cloning the repository:
+You can (and should) run our test suite using [*tox*] (and keep in mind that
`tox run-parallel` is much faster than `tox run`).
+However, you'll probably want a more traditional environment as well.
-```bash
-layout python python$(cat .python-version-default)
-```
+We recommend using the Python version from the `.python-version-default` file
in the project's root directory, because that's the one that is used in the CI
by default, too.
- If you're using tools that understand `.python-version` files like
[*pyenv*](https://github.com/pyenv/pyenv) does, you can make it a link to the
`.python-version-default` file.
+If you're using [*direnv*](https://direnv.net), you can automate the creation
of the project virtual environment with the correct Python version by adding
the following `.envrc` to the project root:
----
-
-[Create a fork](https://github.com/pyca/service-identity/fork) of the
*service-identity* repository and clone it:
-
-```console
-$ git clone [email protected]:YOU/service-identity.git
+```bash
+layout python python$(cat .python-version-default)
```
-Or if you prefer to use Git via HTTPS:
+or, if you like [*uv*](https://github.com/astral-sh/uv):
-```console
-$ git clone https://github.com/YOU/service-identity.git
+```bash
+test -d .venv || (uv venv --python $(cat .python-version-default) && uv pip
install -e . --group dev)
+. .venv/bin/activate
```
-> **Warning**
+> [!WARNING]
> - **Before** you start working on a new pull request, use the "*Sync fork*"
> button in GitHub's web UI to ensure your fork is up to date.
> - **Always create a new branch off `main` for each new pull request.**
> Yes, you can work on `main` in your fork and submit pull requests.
> But this will *inevitably* lead to you not being able to synchronize your
> fork with upstream and having to start over.
+>
+> See also: [Why you shouldn't use your `main` branch for pull
requests](https://hynek.me/articles/pull-requests-branch/).
-Change into the newly created directory and after activating a virtual
environment, install an editable version of *service-identity* along with its
tests requirements:
+Change into the newly created directory and after activating a virtual
environment, install an editable version of *service-identity* along with its
tests requirements (requires Pip 25.1 or *uv* 0.4.27 or later that added
support for [dependency groups](https://peps.python.org/pep-0735/)):
```console
-$ cd service-identity
-$ python -Im pip install --upgrade pip wheel # PLEASE don't skip this step
-$ python -Im pip install -e '.[dev]'
+$ pip install -e . --group dev # or `uv pip install -e . --group dev`
```
-At this point,
+Now you can run the test suite:
```console
$ python -Im pytest
@@ -85,18 +100,21 @@
$ tox run -e docs-watch
```
-... to watch your files and automatically rebuild when a file changes.
-And use:
+This will build the documentation, watch for changes, and rebuild it whenever
you save a file.
+
+To just build the documentation and exit immediately use:
```console
-$ tox run -e docs
+$ tox run -e docs-build
```
-... to build it once and run our doctests.
+You will find the built documentation in `docs/_build/html`.
-The built documentation can then be found in `docs/_build/html/`.
+To run doctests:
----
+```console
+$ tox run -e docs-doctests
+```
To avoid committing code that violates our style guide, we strongly advise you
to install [*pre-commit*] and its hooks:
@@ -234,3 +252,4 @@
[semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/
[*reStructuredText*]:
https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html
[Markdown]:
https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax
+[llm]: AI_POLICY.md
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/FUNDING.yml
new/service-identity-26.1.0/.github/FUNDING.yml
--- old/service-identity-24.2.0/.github/FUNDING.yml 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/FUNDING.yml 2026-05-30
13:50:32.000000000 +0200
@@ -1,3 +1,5 @@
---
github: hynek
tidelift: "pypi/service-identity"
+thanks_dev: u/gh/hynek
+custom: https://hynek.me/say-thanks/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/.github/PULL_REQUEST_TEMPLATE.md
new/service-identity-26.1.0/.github/PULL_REQUEST_TEMPLATE.md
--- old/service-identity-24.2.0/.github/PULL_REQUEST_TEMPLATE.md
1970-01-01 01:00:00.000000000 +0100
+++ new/service-identity-26.1.0/.github/PULL_REQUEST_TEMPLATE.md
2026-05-30 13:50:32.000000000 +0200
@@ -0,0 +1,33 @@
+# Summary
+
+<!-- Please tell us what your pull request is about here. -->
+
+
+# Pull Request Check List
+
+<!--
+This list is our brown M&M test:
+Ignoring -- or even deleting -- leads to instant closing of this pull request.
+The only exceptions are pure documentation fixes.
+
+Please read our [contribution
guide](https://github.com/pyca/service-identity/blob/main/.github/CONTRIBUTING.md)
at least once; it will save you unnecessary review cycles!
+
+You may check boxes that don't apply to your pull request to indicate that
there isn't anything left to do.
+-->
+
+- [ ] I acknowledge this project's [**AI
policy**](https://github.com/pyca/service-identity/blob/main/.github/AI_POLICY.md).
+- [ ] This pull requests is [**not** from my `main`
branch](https://hynek.me/articles/pull-requests-branch/).
+ - Consider granting [push permissions to the PR
branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork),
so maintainers can fix minor issues themselves without pestering you.
+- [ ] There's **tests** for all new and changed code.
+- [ ] **New APIs** are added to our typing tests in
[`api.py`](https://github.com/pyca/service-identity/blob/main/tests/typing/api.py).
+- [ ] Updated **documentation** for changed code.
+ - [ ] New functions/classes have to be added to `docs/api.rst` by hand.
+ - [ ] Changed/added classes/methods/functions have appropriate
`versionadded`, `versionchanged`, or `deprecated`
[directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).
+ - The next version is the second number in the current release + 1. The
first number represents the current year. So if the current version on PyPI is
26.1.0, the next version is gonna be 26.2.0. If the next version is the first
in the new year, it'll be 27.1.0.
+- [ ] Documentation in `.rst` and `.md` files is written using [**semantic
newlines**](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
+- [ ] Changes (and possible deprecations) are documented in the
[**changelog**](https://github.com/pyca/service-identity/blob/main/CHANGELOG.md).
+
+<!--
+If you have *any* questions to *any* of the points above, just **submit and
ask**!
+Given the ongoing AI slop wave we need to be strict about policies, but we're
happy to help out fellow humans.
+-->
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/dependabot.yml
new/service-identity-26.1.0/.github/dependabot.yml
--- old/service-identity-24.2.0/.github/dependabot.yml 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/dependabot.yml 2026-05-30
13:50:32.000000000 +0200
@@ -1,7 +1,14 @@
---
version: 2
updates:
- - package-ecosystem: "github-actions"
- directory: "/"
+ - package-ecosystem: github-actions
+ directory: /
schedule:
- interval: "monthly"
+ interval: monthly
+ cooldown:
+ #
https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns
+ default-days: 7
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/workflows/ci.yml
new/service-identity-26.1.0/.github/workflows/ci.yml
--- old/service-identity-24.2.0/.github/workflows/ci.yml 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/workflows/ci.yml 2026-05-30
13:50:32.000000000 +0200
@@ -4,12 +4,11 @@
on:
push:
branches: [main]
- tags: ["*"]
pull_request:
workflow_dispatch:
env:
- FORCE_COLOR: "1" # Make tools pretty.
+ FORCE_COLOR: "1" # Make tools pretty.
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_NO_PYTHON_VERSION_WARNING: "1"
@@ -18,18 +17,17 @@
jobs:
lint:
- name: Run linters
+ name: Run pre-commit
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
+ with:
+ persist-credentials: false
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
python-version-file: .python-version-default
- - uses: hynek/setup-cached-uv@v2
- - run: >
- uvx --with tox-uv
- tox run -e lint -- --show-diff-on-failure
+ - uses:
tox-dev/action-pre-commit-uv@41a04ab74d5ec7ca33c8db8a59b6e3291d576033 # v1.0.4
build-package:
@@ -37,11 +35,12 @@
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
with:
fetch-depth: 0
+ persist-credentials: false
- - uses: hynek/build-and-inspect-python-package@v2
+ - uses:
hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df
# v2.18.0
id: baipp
outputs:
@@ -54,35 +53,39 @@
name: Tests & Mypy API on ${{ matrix.python-version }}
runs-on: ubuntu-latest
needs: build-package
+
strategy:
+ fail-fast: false
matrix:
# Created by the build-and-inspect-python-package action above.
python-version: ${{
fromJson(needs.build-package.outputs.python-versions) }}
+ env:
+ PYTHON: ${{ matrix.python-version }}
+
steps:
- name: Download pre-built packages
- uses: actions/download-artifact@v4
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Packages
path: dist
- run: |
tar xf dist/*.tar.gz --strip-components=1
rm -rf src
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- - uses: hynek/setup-cached-uv@v2
+ - uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 #
v2.5.0
- name: Run tests
run: >
- uvx --with tox-uv
- tox run
+ uvx --with tox-uv tox run
--installpkg dist/*.whl
- -f py$(echo ${{ matrix.python-version }} | tr -d .)
+ -f py${PYTHON//./}
- name: Upload coverage data
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
# v7.0.1
with:
name: coverage-data-${{ matrix.python-version }}
path: .coverage.*
@@ -91,32 +94,35 @@
- name: Check public API with Mypy
run: >
- uvx --with tox-uv
- tox run
+ uvx --with tox-uv tox run
--installpkg dist/*.whl
-e mypy-api
coverage:
name: Ensure 100% test coverage
- needs: tests
runs-on: ubuntu-latest
+ needs: tests
+ if: always()
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
+ with:
+ persist-credentials: false
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
python-version-file: .python-version-default
- - uses: hynek/setup-cached-uv@v2
+ - uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 #
v2.5.0
- - uses: actions/download-artifact@v4
+ - name: Download coverage data
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: coverage-data-*
merge-multiple: true
- - name: Combine coverage & fail if it's <100%
+ - name: Combine coverage and fail if it's <100%.
run: |
- uv tool install coverage[toml]
+ uv tool install coverage
coverage combine
coverage html --skip-covered --skip-empty
@@ -128,7 +134,7 @@
coverage report --fail-under=100
- name: Upload HTML report if check failed.
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
# v7.0.1
with:
name: html-report
path: htmlcov
@@ -142,15 +148,15 @@
steps:
- name: Download pre-built packages
- uses: actions/download-artifact@v4
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Packages
path: dist
- run: tar xf dist/*.tar.gz --strip-components=1
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
python-version-file: .python-version-default
- - uses: hynek/setup-cached-uv@v2
+ - uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 #
v2.5.0
- run: >
uvx --with tox-uv
@@ -160,21 +166,22 @@
install-dev:
strategy:
matrix:
- os: [ubuntu-latest, windows-latest, macos-latest]
+ os: [ubuntu-latest, windows-latest]
name: Verify dev env
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
+ with:
+ persist-credentials: false
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
python-version-file: .python-version-default
- cache: pip
- name: Install in dev mode & import
run: |
- python -Im pip install -e .[dev]
+ python -Im pip install --group dev -e .
python -Ic 'import service_identity;
print(service_identity.__version__)'
python -Ic 'import service_identity.pyopenssl'
python -Ic 'import service_identity.cryptography'
@@ -187,20 +194,20 @@
steps:
- name: Download pre-built packages
- uses: actions/download-artifact@v4
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Packages
path: dist
- run: tar xf dist/*.tar.gz --strip-components=1
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
# Keep in sync with tox.ini/docs & .readthedocs.yaml
- python-version: "3.12"
- - uses: hynek/setup-cached-uv@v2
+ python-version: "3.14"
+ - uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 #
v2.5.0
- run: >
uvx --with tox-uv
- tox run -e docs
+ tox run -e docs-build,docs-doctests
required-checks-pass:
@@ -209,15 +216,15 @@
needs:
- coverage
- - docs
- - install-dev
- lint
- mypy-pkg
+ - docs
+ - install-dev
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
- uses: re-actors/alls-green@release/v1
+ uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe #
v1.2.2
with:
jobs: ${{ toJSON(needs) }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/.github/workflows/codeql-analysis.yml
new/service-identity-26.1.0/.github/workflows/codeql-analysis.yml
--- old/service-identity-24.2.0/.github/workflows/codeql-analysis.yml
2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/workflows/codeql-analysis.yml
2026-05-30 13:50:32.000000000 +0200
@@ -2,10 +2,6 @@
name: CodeQL
on:
- push:
- branches: [main]
- pull_request:
- branches: [main]
schedule:
- cron: "41 3 * * 6"
@@ -28,15 +24,17 @@
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
+ with:
+ persist-credentials: false
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses:
github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v3
+ uses:
github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa #
v4.36.0
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses:
github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/.github/workflows/pypi-package.yml
new/service-identity-26.1.0/.github/workflows/pypi-package.yml
--- old/service-identity-24.2.0/.github/workflows/pypi-package.yml
2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.github/workflows/pypi-package.yml
2026-05-30 13:50:32.000000000 +0200
@@ -1,5 +1,5 @@
---
-name: Build & maybe upload PyPI package
+name: Build & upload PyPI package
on:
push:
@@ -10,23 +10,23 @@
- published
workflow_dispatch:
-permissions:
- attestations: write
- contents: read
- id-token: write
jobs:
# Always build & lint package.
build-package:
name: Build & verify package
runs-on: ubuntu-latest
+ permissions:
+ attestations: write
+ id-token: write
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
with:
fetch-depth: 0
+ persist-credentials: false
- - uses: hynek/build-and-inspect-python-package@v2
+ - uses:
hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df
# v2.18.0
with:
attest-build-provenance-github: 'true'
@@ -34,19 +34,22 @@
release-test-pypi:
name: Publish in-dev package to test.pypi.org
environment: release-test-pypi
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+ if: github.repository_owner == 'pyca' && github.event_name == 'push' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: build-package
+ permissions:
+ id-token: write
+
steps:
- name: Download packages built by build-and-inspect-python-package
- uses: actions/download-artifact@v4
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Packages
path: dist
- name: Upload package to Test PyPI
- uses: pypa/gh-action-pypi-publish@release/v1
+ uses:
pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
repository-url: https://test.pypi.org/legacy/
@@ -54,16 +57,19 @@
release-pypi:
name: Publish released package to pypi.org
environment: release-pypi
- if: github.event.action == 'published'
+ if: github.repository_owner == 'pyca' && github.event.action == 'published'
runs-on: ubuntu-latest
needs: build-package
+ permissions:
+ id-token: write
+
steps:
- name: Download packages built by build-and-inspect-python-package
- uses: actions/download-artifact@v4
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Packages
path: dist
- name: Upload package to PyPI
- uses: pypa/gh-action-pypi-publish@release/v1
+ uses:
pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.github/workflows/zizmor.yml
new/service-identity-26.1.0/.github/workflows/zizmor.yml
--- old/service-identity-24.2.0/.github/workflows/zizmor.yml 1970-01-01
01:00:00.000000000 +0100
+++ new/service-identity-26.1.0/.github/workflows/zizmor.yml 2026-05-30
13:50:32.000000000 +0200
@@ -0,0 +1,39 @@
+# https://github.com/woodruffw/zizmor
+name: Zizmor
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["*"]
+
+permissions:
+ contents: read
+
+
+jobs:
+ zizmor:
+ name: Zizmor latest via PyPI
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
+ with:
+ persist-credentials: false
+ - uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 #
v2.5.0
+
+ - name: Run zizmor 🌈
+ run: uvx zizmor --format sarif . > results.sarif
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Upload SARIF file
+ uses:
github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa #
v4.36.0
+ with:
+ # Path to SARIF file relative to the root of the repository
+ sarif_file: results.sarif
+ # Optional category for the results
+ # Used to differentiate multiple results for one commit
+ category: zizmor
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.pre-commit-config.yaml
new/service-identity-26.1.0/.pre-commit-config.yaml
--- old/service-identity-24.2.0/.pre-commit-config.yaml 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.pre-commit-config.yaml 2026-05-30
13:50:32.000000000 +0200
@@ -1,14 +1,14 @@
---
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.7.1
+ rev: v0.15.15
hooks:
- - id: ruff
+ - id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
- rev: v2.3.0
+ rev: v2.4.2
hooks:
- id: codespell
args: [-L, fo]
@@ -20,7 +20,7 @@
args: [tests]
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
+ rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.python-version-default
new/service-identity-26.1.0/.python-version-default
--- old/service-identity-24.2.0/.python-version-default 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.python-version-default 2026-05-30
13:50:32.000000000 +0200
@@ -1 +1 @@
-3.13
+3.14
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/.readthedocs.yaml
new/service-identity-26.1.0/.readthedocs.yaml
--- old/service-identity-24.2.0/.readthedocs.yaml 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/.readthedocs.yaml 2026-05-30
13:50:32.000000000 +0200
@@ -4,16 +4,17 @@
build:
os: ubuntu-lts-latest
tools:
- # Keep in-sync with tox.ini/docs and ci.yml/docs
- python: "3.12"
+ # Keep version in sync with tox.ini/docs and ci.yml/docs.
+ python: "3.14"
jobs:
- # Need the tags to calculate the version (sometimes).
- post_checkout:
+ create_environment:
+ # Need the tags to calculate the version (sometimes).
- git fetch --tags
-python:
- install:
- - method: pip
- path: .
- extra_requirements:
- - docs
+ - asdf plugin add uv
+ - asdf install uv latest
+ - asdf global uv latest
+
+ build:
+ html:
+ - uvx --with tox-uv tox run -e docs-build -- $READTHEDOCS_OUTPUT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/CHANGELOG.md
new/service-identity-26.1.0/CHANGELOG.md
--- old/service-identity-24.2.0/CHANGELOG.md 2024-10-26 09:10:38.000000000
+0200
+++ new/service-identity-26.1.0/CHANGELOG.md 2026-05-30 13:50:32.000000000
+0200
@@ -13,6 +13,28 @@
<!-- changelog follows -->
+## [26.1.0](https://github.com/pyca/service-identity/compare/24.2.0...26.1.0)
- 2026-05-30
+
+### Added
+
+- Python 3.14 and 3.15 are now officially supported.
+ [#85](https://github.com/pyca/service-identity/pull/85)
+ [#93](https://github.com/pyca/service-identity/pull/93)
+
+
+### Changed
+
+- *service-identity* now uses *cryptography*'s Rust-based ASN.1 decoder and
doesn't depend on *pyasn1* and *pyasn1-modules* anymore.
+ As a result, the oldest supported pyOpenSSL backend combination is now
*pyOpenSSL* 26.1.0 with *cryptography* 47.0.0.
+ [#95](https://github.com/pyca/service-identity/pull/95)
+
+
+### Fixed
+
+- Verifying a single-label hostname (e.g. `localhost`) against a wildcard
certificate pattern now raises `VerificationError` cleanly instead of crashing
with an opaque `ValueError`.
+ [#92](https://github.com/pyca/service-identity/pull/92)
+
+
## [24.2.0](https://github.com/pyca/service-identity/compare/24.1.0...24.2.0)
- 2024-10-26
### Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/README.md
new/service-identity-26.1.0/README.md
--- old/service-identity-24.2.0/README.md 2024-10-26 09:10:38.000000000
+0200
+++ new/service-identity-26.1.0/README.md 2026-05-30 13:50:32.000000000
+0200
@@ -5,6 +5,7 @@
<a href="https://pypi.org/project/service-identity/"><img
src="https://img.shields.io/pypi/v/service-identity" alt="PyPI release" /></a>
<a href="https://pepy.tech/project/service-identity"><img
src="https://static.pepy.tech/badge/service-identity/month" alt="Downloads per
month" /></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/7462"><img
src="https://bestpractices.coreinfrastructure.org/projects/7462/badge" /></a>
+<a
href="https://github.com/pyca/service-identity/blob/main/.github/AI_POLICY.md"><img
src="https://img.shields.io/badge/no-slop-purple" alt="No AI slop inside."></a>
<a
href="https://www.irccloud.com/invite?channel=%23pyca&hostname=irc.libera.chat&port=6697&ssl=1"><img
src="https://www.irccloud.com/invite-svg?channel=%23pyca&hostname=irc.libera.chat&port=6697&ssl=1"
alt="PyCA on IRC" /></a>
<!-- spiel-begin -->
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/docs/conf.py
new/service-identity-26.1.0/docs/conf.py
--- old/service-identity-24.2.0/docs/conf.py 2024-10-26 09:10:38.000000000
+0200
+++ new/service-identity-26.1.0/docs/conf.py 2026-05-30 13:50:32.000000000
+0200
@@ -59,6 +59,9 @@
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
+nitpick_ignore = [
+ ("py:class", "cryptography.hazmat.bindings._rust.x509.Certificate"),
+]
# -- Options for HTML output ----------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/pyproject.toml
new/service-identity-26.1.0/pyproject.toml
--- old/service-identity-24.2.0/pyproject.toml 2024-10-26 09:10:38.000000000
+0200
+++ new/service-identity-26.1.0/pyproject.toml 2026-05-30 13:50:32.000000000
+0200
@@ -4,8 +4,10 @@
[project]
name = "service-identity"
+dynamic = ["version", "readme"]
authors = [{ name = "Hynek Schlawack", email = "[email protected]" }]
license = "MIT"
+license-files = ["LICENSE"]
requires-python = ">=3.8"
description = "Service identity verification for pyOpenSSL & cryptography."
keywords = ["cryptography", "openssl", "pyopenssl"]
@@ -19,6 +21,8 @@
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "Programming Language :: Python :: 3.15",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Security :: Cryptography",
@@ -28,18 +32,8 @@
dependencies = [
# Keep in-sync with tests/constraints/*.
"attrs>=19.1.0",
- "pyasn1-modules",
- "pyasn1",
- "cryptography",
+ "cryptography>=47",
]
-dynamic = ["version", "readme"]
-
-[project.optional-dependencies]
-idna = ["idna"]
-tests = ["coverage[toml]>=5.0.2", "pytest"]
-docs = ["sphinx", "furo", "myst-parser", "sphinx-notfound-page", "pyOpenSSL"]
-mypy = ["mypy", "types-pyOpenSSL", "idna"]
-dev = ["service-identity[tests,mypy,idna]", "pyOpenSSL"]
[project.urls]
Documentation = "https://service-identity.readthedocs.io/"
@@ -50,6 +44,20 @@
Mastodon = "https://mastodon.social/@hynek"
Twitter = "https://twitter.com/hynek"
+[project.optional-dependencies]
+idna = ["idna"]
+
+
+[dependency-groups]
+tests = ["coverage[toml]>=5.0.2", "pytest"]
+docs = ["sphinx", "furo", "myst-parser", "sphinx-notfound-page", "pyOpenSSL"]
+mypy = ["mypy", "types-pyOpenSSL", "idna"]
+dev = [
+ { include-group = "tests" },
+ { include-group = "mypy" },
+ "pyOpenSSL",
+]
+
[tool.hatch.version]
source = "vcs"
@@ -96,7 +104,7 @@
[tool.coverage.run]
parallel = true
branch = true
-source = ["service_identity"]
+source = ["service_identity", "tests"]
[tool.coverage.paths]
source = ["src", ".tox/py*/**/site-packages"]
@@ -104,10 +112,7 @@
[tool.coverage.report]
show_missing = true
skip_covered = true
-exclude_lines = [
- # a more strict default pragma
- "\\# pragma: no cover\\b",
-
+exclude_also = [
# allow defensive code
"^\\s*raise AssertionError\\b",
"^\\s*raise NotImplementedError\\b",
@@ -119,6 +124,9 @@
": \\.\\.\\.(\\s*#.*)?$",
"^ +\\.\\.\\.$",
"-> ['\"]?NoReturn['\"]?:",
+
+ # Tests
+ '^if pytest\.importorskip\(',
]
@@ -176,6 +184,11 @@
lines-after-imports = 2
+[[tool.ty.overrides]]
+include = ["tests"]
+rules.all = "ignore"
+
+
[tool.mypy]
strict = true
pretty = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/src/service_identity/__init__.py
new/service-identity-26.1.0/src/service_identity/__init__.py
--- old/service-identity-24.2.0/src/service_identity/__init__.py
2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/src/service_identity/__init__.py
2026-05-30 13:50:32.000000000 +0200
@@ -22,8 +22,8 @@
"CertificateError",
"SubjectAltNameWarning",
"VerificationError",
- "hazmat",
"cryptography",
+ "hazmat",
"pyopenssl",
]
@@ -33,6 +33,6 @@
msg = f"module {__name__} has no attribute {name}"
raise AttributeError(msg)
- from importlib.metadata import version
+ from importlib.metadata import version # noqa: PLC0415
return version("service-identity")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/src/service_identity/cryptography.py
new/service-identity-26.1.0/src/service_identity/cryptography.py
--- old/service-identity-24.2.0/src/service_identity/cryptography.py
2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/src/service_identity/cryptography.py
2026-05-30 13:50:32.000000000 +0200
@@ -8,6 +8,7 @@
from typing import Sequence
+from cryptography.hazmat import asn1
from cryptography.x509 import (
Certificate,
DNSName,
@@ -18,8 +19,6 @@
UniformResourceIdentifier,
)
from cryptography.x509.extensions import ExtensionNotFound
-from pyasn1.codec.der.decoder import decode
-from pyasn1.type.char import IA5String
from .exceptions import CertificateError
from .hazmat import (
@@ -159,12 +158,13 @@
)
for other in ext.value.get_values_for_type(OtherName):
if other.type_id == ID_ON_DNS_SRV:
- srv, _ = decode(other.value)
- if isinstance(srv, IA5String):
- ids.append(SRVPattern.from_bytes(srv.asOctets()))
- else: # pragma: no cover
+ try:
+ srv = asn1.decode_der(asn1.IA5String, other.value)
+ except ValueError as e:
msg = "Unexpected certificate content."
- raise CertificateError(msg)
+ raise CertificateError(msg) from e
+
+ ids.append(SRVPattern.from_bytes(srv.as_str().encode("ascii")))
return ids
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/src/service_identity/hazmat.py
new/service-identity-26.1.0/src/service_identity/hazmat.py
--- old/service-identity-24.2.0/src/service_identity/hazmat.py 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/src/service_identity/hazmat.py 2026-05-30
13:50:32.000000000 +0200
@@ -427,6 +427,8 @@
:return: `True` if *cert_pattern* matches *actual_hostname*, else `False`.
"""
if b"*" in cert_pattern:
+ if b"." not in actual_hostname:
+ return False
cert_head, cert_tail = cert_pattern.split(b".", 1)
actual_head, actual_tail = actual_hostname.split(b".", 1)
if cert_tail != actual_tail:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/tests/constraints/oldest-cryptography.txt
new/service-identity-26.1.0/tests/constraints/oldest-cryptography.txt
--- old/service-identity-24.2.0/tests/constraints/oldest-cryptography.txt
2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/tests/constraints/oldest-cryptography.txt
2026-05-30 13:50:32.000000000 +0200
@@ -1,2 +1,2 @@
attrs==19.1.0
-cryptography<3
+cryptography==47.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/service-identity-24.2.0/tests/constraints/oldest-pyopenssl.txt
new/service-identity-26.1.0/tests/constraints/oldest-pyopenssl.txt
--- old/service-identity-24.2.0/tests/constraints/oldest-pyopenssl.txt
2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/tests/constraints/oldest-pyopenssl.txt
2026-05-30 13:50:32.000000000 +0200
@@ -1,3 +1,3 @@
attrs==19.1.0
-cryptography<35
-pyOpenSSL==17.1.0
+cryptography==47.0.0
+pyOpenSSL==26.1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/tests/test_cryptography.py
new/service-identity-26.1.0/tests/test_cryptography.py
--- old/service-identity-24.2.0/tests/test_cryptography.py 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/tests/test_cryptography.py 2026-05-30
13:50:32.000000000 +0200
@@ -3,9 +3,15 @@
import pytest
from cryptography.hazmat.backends import default_backend
-from cryptography.x509 import load_pem_x509_certificate
+from cryptography.x509 import (
+ ExtensionOID,
+ OtherName,
+ SubjectAlternativeName,
+ load_pem_x509_certificate,
+)
from service_identity.cryptography import (
+ ID_ON_DNS_SRV,
extract_ids,
extract_patterns,
verify_certificate_hostname,
@@ -22,6 +28,7 @@
DNSPattern,
IPAddress_ID,
IPAddressPattern,
+ SRVPattern,
URIPattern,
)
@@ -48,7 +55,7 @@
"""
with pytest.raises(
CertificateError,
- match="Certificate does not contain any `subjectAltName`s.",
+ match="Certificate does not contain any `subjectAltName`s",
):
verify_certificate_hostname(X509_CN_ONLY, "example.com")
@@ -60,7 +67,7 @@
"""
with pytest.raises(
CertificateError,
- match="Certificate does not contain any `subjectAltName`s.",
+ match="Certificate does not contain any `subjectAltName`s",
):
verify_certificate_ip_address(X509_CN_ONLY, ip)
@@ -130,6 +137,48 @@
id for id in rv if isinstance(id, URIPattern)
]
+ def test_srv(self):
+ """
+ Returns the correct SRVPattern from a certificate.
+ """
+ rv = extract_patterns(X509_OTHER_NAME)
+ assert [SRVPattern.from_bytes(b"_xmpp-client.example.net")] == [
+ id for id in rv if isinstance(id, SRVPattern)
+ ]
+
+ def test_malformed_srv_other_name(self):
+ """
+ Malformed DER in a SRV-ID otherName raises a CertificateError.
+ """
+
+ class Extension:
+ def __init__(self, value):
+ self.value = value
+
+ class Extensions:
+ def __init__(self, value):
+ self._value = value
+
+ def get_extension_for_oid(self, oid):
+ assert oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME
+
+ return Extension(self._value)
+
+ class Certificate:
+ def __init__(self, san):
+ self.extensions = Extensions(san)
+
+ cert = Certificate(
+ SubjectAlternativeName(
+ [OtherName(ID_ON_DNS_SRV, b"\x16\x03abc\x00")]
+ )
+ )
+
+ with pytest.raises(
+ CertificateError, match=r"Unexpected certificate content\."
+ ):
+ extract_patterns(cert)
+
def test_ip(self):
"""
Returns IP patterns.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/tests/test_hazmat.py
new/service-identity-26.1.0/tests/test_hazmat.py
--- old/service-identity-24.2.0/tests/test_hazmat.py 2024-10-26
09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/tests/test_hazmat.py 2026-05-30
13:50:32.000000000 +0200
@@ -51,7 +51,7 @@
"""
with pytest.raises(
CertificateError,
- match="Certificate does not contain any `subjectAltName`s.",
+ match="Certificate does not contain any `subjectAltName`s",
):
verify_service_identity(
cert_patterns=[], obligatory_ids=[], optional_ids=[]
@@ -81,6 +81,22 @@
verify_service_identity(
DNS_IDS, obligatory_ids=[i], optional_ids=[]
)
+
+ assert [DNSMismatch(mismatched_id=i)] == e.value.errors
+
+ def test_single_label_hostname_mismatch(self):
+ """
+ A single-label hostname that does not match a wildcard cert pattern
+ raises VerificationError, not a raw ValueError from the matcher.
+ """
+ i = DNS_ID("localhost")
+ with pytest.raises(VerificationError) as e:
+ verify_service_identity(
+ [DNSPattern.from_bytes(b"*.example.com")],
+ obligatory_ids=[i],
+ optional_ids=[],
+ )
+
assert [DNSMismatch(mismatched_id=i)] == e.value.errors
def test_ip_address_success(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/service-identity-24.2.0/tox.ini
new/service-identity-26.1.0/tox.ini
--- old/service-identity-24.2.0/tox.ini 2024-10-26 09:10:38.000000000 +0200
+++ new/service-identity-26.1.0/tox.ini 2026-05-30 13:50:32.000000000 +0200
@@ -1,18 +1,18 @@
[tox]
-min_version = 4
+min_version = 4.22
env_list =
lint,
mypy-{api,pkg},
- docs,
- py3{8,9,10,11,12,13}{,-pyopenssl}{,-oldest}{,-idna},
- coverage-report
+ docs-{build,doctests},
+ py3{8-15}{,-pyopenssl}{,-oldest}{,-idna},
+ coverage-{combine,report}
[testenv]
package = wheel
wheel_build_env = .pkg
+dependency_groups = tests
extras =
- tests
idna: idna
deps =
pyopenssl: pyopenssl
@@ -21,50 +21,64 @@
NO_COLOR
set_env =
oldest: PIP_CONSTRAINT = tests/constraints/oldest-cryptography.txt
+ oldest: UV_CONSTRAINT = tests/constraints/oldest-cryptography.txt
pyopenssl-oldest: PIP_CONSTRAINT = tests/constraints/oldest-pyopenssl.txt
+ pyopenssl-oldest: UV_CONSTRAINT = tests/constraints/oldest-pyopenssl.txt
commands =
coverage run -m pytest {posargs}
py312-pyopenssl-latest-idna: coverage run -m pytest --doctest-modules
--doctest-glob='*.rst' {posargs}
+# Split combine/report in 2 to avoid excessive "Combined data file ..." output.
+[testenv:coverage-combine]
+# keep in-sync with .python-version-default
+base_python = py314
+depends = py3*
+skip_install = true
+deps = coverage
+commands = coverage combine
+
+
[testenv:coverage-report]
# keep in-sync with .python-version-default
-base_python = py312
-deps = coverage[toml]>=5.0.2
+base_python = py314
+depends = coverage-combine
+parallel_show_output = true
skip_install = true
-commands =
- coverage combine
- coverage report
+deps = coverage
+commands = coverage report
[testenv:lint]
skip_install = true
-deps = pre-commit-uv
-commands = pre-commit run --all-files {posargs}
+deps = prek
+commands = prek run --all-files {posargs}
[testenv:mypy-api]
-extras = mypy
+dependency_groups = mypy
commands = mypy tests/typing docs/pyopenssl_example.py
[testenv:mypy-pkg]
-extras = mypy
+dependency_groups = mypy
commands = mypy src
-[testenv:docs]
-# Keep in-sync with gh-actions and .readthedocs.yaml.
-base_python = py312
-extras = docs
+[testenv:docs-{build,doctests,linkcheck}]
+# Keep base_python in sync with gh-actions and .readthedocs.yaml.
+base_python = py314
+dependency_groups = docs
commands =
- sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
- sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
+ # N.B. doctests is not a nitpicky as build -- we need to run both in CI!
+ build: sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs
{posargs:docs/_build/}html
+ doctests: sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs
{posargs:docs/_build/}html
+ linkcheck: sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs
docs/_build/html
[testenv:docs-watch]
package = editable
-base_python = {[testenv:docs]base_python}
-extras = {[testenv:docs]extras}
+base_python = {[testenv:docs-build]base_python}
+dependency_groups = {[testenv:docs-build]dependency_groups}
deps = watchfiles
commands =
watchfiles \
@@ -72,8 +86,3 @@
'sphinx-build -W -n --jobs auto -b html -d {envtmpdir}/doctrees docs
docs/_build/html' \
src \
docs
-
-[testenv:docs-linkcheck]
-base_python = {[testenv:docs]base_python}
-extras = {[testenv:docs]extras}
-commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs
docs/_build/html