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&amp;hostname=irc.libera.chat&amp;port=6697&amp;ssl=1";><img
 
src="https://www.irccloud.com/invite-svg?channel=%23pyca&amp;hostname=irc.libera.chat&amp;port=6697&amp;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

Reply via email to