Skip to content

Commit

Permalink
Add type hints
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Jun 12, 2023
1 parent 7578496 commit e3ed15f
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 104 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ jobs:
strategy:
matrix:
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
Expand All @@ -56,7 +55,7 @@ jobs:
allow-prereleases: true
cache: pip

- name: Install & run tox
- name: Prepare tox
run: |
V=${{ matrix.python-version }}
Expand All @@ -67,7 +66,9 @@ jobs:
fi
python -Im pip install tox
python -Im tox run -f "$V"
- run: python -Im tox run -f "$V"
- run: python -Im tox run -e mypy

- name: Upload coverage data
uses: actions/upload-artifact@v3
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ If breaking changes are needed do be done, they are:

### Backwards-incompatible Changes

- All Python versions up to and including 3.6 have been dropped.
- All Python versions up to and including 3.7 have been dropped.
- Support for `commonName` in certificates has been dropped.
It has been deprecated since 2017 and isn't supported by any major browser.
- The oldest supported pyOpenSSL version (when using the `pyopenssl` backend) is now 17.0.0.
Expand All @@ -33,6 +33,8 @@ If breaking changes are needed do be done, they are:
- `service_identity.(cryptography|pyopenssl).extract_patterns()` are now public APIs (FKA `extract_ids()`).
You can use them to extract the patterns from a certificate without verifying anything.
[#55](https://github.com/pyca/service-identity/pull/55)
- *service-identity* is now fully typed.
[#57](https://github.com/pyca/service-identity/pull/57)


## 21.1.0 (2021-05-09)
Expand Down
11 changes: 4 additions & 7 deletions docs/pyopenssl_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
hostname = sys.argv[1]

ctx = SSL.Context(SSL.TLSv1_2_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, lambda conn, cert, errno, depth, ok: ok)
ctx.set_verify(SSL.VERIFY_PEER, lambda conn, cert, errno, depth, ok: bool(ok))
ctx.set_default_verify_paths()

conn = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
Expand All @@ -22,12 +22,9 @@
try:
conn.do_handshake()

print("Server certificate is valid for the following patterns:\n")
pprint.pprint(
service_identity.pyopenssl.extract_patterns(
conn.get_peer_certificate()
)
)
if cert := conn.get_peer_certificate():
print("Server certificate is valid for the following patterns:\n")
pprint.pprint(service_identity.pyopenssl.extract_patterns(cert))

try:
service_identity.pyopenssl.verify_hostname(conn, hostname)
Expand Down
46 changes: 42 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ build-backend = "hatchling.build"
name = "service-identity"
authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }]
license = "MIT"
requires-python = ">=3.7"
requires-python = ">=3.8"
description = "Service identity verification for pyOpenSSL & cryptography."
keywords = ["cryptography", "openssl", "pyopenssl"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand All @@ -23,22 +22,23 @@ classifiers = [
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Security :: Cryptography",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
]
dependencies = [
# Keep in-sync with tests/constraints/*.
"attrs>=19.1.0",
"pyasn1-modules",
"pyasn1",
"cryptography",
"importlib_metadata;python_version<'3.8'",
]
dynamic = ["version", "readme"]

[project.optional-dependencies]
idna = ["idna"]
tests = ["coverage[toml]>=5.0.2", "pytest"]
docs = ["sphinx", "furo", "myst-parser", "sphinx-notfound-page"]
dev = ["service-identity[tests,docs,idna]", "pyOpenSSL"]
mypy = ["mypy", "types-pyOpenSSL", "idna"]
dev = ["service-identity[tests,mypy,docs,idna]", "pyOpenSSL"]

[project.urls]
Documentation = "https://service-identity.readthedocs.io/"
Expand Down Expand Up @@ -105,6 +105,22 @@ source = ["src", ".tox/py*/**/site-packages"]
[tool.coverage.report]
show_missing = true
skip_covered = true
exclude_lines = [
# a more strict default pragma
"\\# pragma: no cover\\b",

# allow defensive code
"^\\s*raise AssertionError\\b",
"^\\s*raise NotImplementedError\\b",
"^\\s*return NotImplemented\\b",
"^\\s*raise$",

# typing-related code
"^if (False|TYPE_CHECKING):",
": \\.\\.\\.(\\s*#.*)?$",
"^ +\\.\\.\\.$",
"-> ['\"]?NoReturn['\"]?:",
]


[tool.black]
Expand Down Expand Up @@ -152,3 +168,25 @@ ignore = [
[tool.ruff.isort]
lines-between-types = 1
lines-after-imports = 2


[tool.mypy]
strict = true

show_error_codes = true
enable_error_code = ["ignore-without-code"]
ignore_missing_imports = true

warn_return_any = false

[[tool.mypy.overrides]]
module = "tests.*"
ignore_errors = true

[[tool.mypy.overrides]]
module = "tests.typing.*"
ignore_errors = false

[[tool.mypy.overrides]]
module = "cryptography.*"
follow_imports = "skip"
6 changes: 1 addition & 5 deletions src/service_identity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,9 @@ def __getattr__(name: str) -> str:
if name not in dunder_to_metadata.keys():
raise AttributeError(f"module {__name__} has no attribute {name}")

import sys
import warnings

if sys.version_info < (3, 8):
from importlib_metadata import metadata
else:
from importlib.metadata import metadata
from importlib.metadata import metadata

warnings.warn(
f"Accessing service_identity.{name} is deprecated and will be "
Expand Down
6 changes: 3 additions & 3 deletions src/service_identity/cryptography.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def verify_certificate_hostname(
"""
verify_service_identity(
cert_patterns=extract_patterns(certificate),
obligatory_ids=[DNS_ID(hostname)],
obligatory_ids=[DNS_ID(hostname)], # type: ignore[list-item]
optional_ids=[],
)

Expand Down Expand Up @@ -89,7 +89,7 @@ def verify_certificate_ip_address(
"""
verify_service_identity(
cert_patterns=extract_patterns(certificate),
obligatory_ids=[IPAddress_ID(ip_address)],
obligatory_ids=[IPAddress_ID(ip_address)], # type: ignore[list-item]
optional_ids=[],
)

Expand Down Expand Up @@ -141,7 +141,7 @@ def extract_patterns(cert: Certificate) -> Sequence[CertificatePattern]:
srv, _ = decode(other.value)
if isinstance(srv, IA5String):
ids.append(SRVPattern.from_bytes(srv.asOctets()))
else: # pragma: nocover
else: # pragma: no cover
raise CertificateError("Unexpected certificate content.")

return ids
Expand Down
50 changes: 25 additions & 25 deletions src/service_identity/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
them from __init__.py.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Sequence


if TYPE_CHECKING:
from .hazmat import ServiceID

import attr

Expand All @@ -23,52 +30,45 @@ class SubjectAltNameWarning(DeprecationWarning):
"""


@attr.s(auto_exc=True)
class VerificationError(Exception):
"""
Service identity verification failed.
"""

errors = attr.ib()

def __str__(self):
return self.__repr__()
@attr.s(slots=True)
class Mismatch:
mismatched_id: ServiceID = attr.ib()


@attr.s
class DNSMismatch:
class DNSMismatch(Mismatch):
"""
No matching DNSPattern could be found.
"""

mismatched_id = attr.ib()


@attr.s
class SRVMismatch:
class SRVMismatch(Mismatch):
"""
No matching SRVPattern could be found.
"""

mismatched_id = attr.ib()


@attr.s
class URIMismatch:
class URIMismatch(Mismatch):
"""
No matching URIPattern could be found.
"""

mismatched_id = attr.ib()


@attr.s
class IPAddressMismatch:
class IPAddressMismatch(Mismatch):
"""
No matching IPAddressPattern could be found.
"""

mismatched_id = attr.ib()

@attr.s(auto_exc=True)
class VerificationError(Exception):
"""
Service identity verification failed.
"""

errors: Sequence[Mismatch] = attr.ib()

def __str__(self) -> str:
return self.__repr__()


class CertificateError(Exception):
Expand Down
Loading

0 comments on commit e3ed15f

Please sign in to comment.