Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-trustme for openSUSE:Factory checked in at 2021-09-04 22:32:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-trustme (Old) and /work/SRC/openSUSE:Factory/.python-trustme.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-trustme" Sat Sep 4 22:32:02 2021 rev:8 rq:915528 version:0.9.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-trustme/python-trustme.changes 2021-05-12 19:31:29.647217547 +0200 +++ /work/SRC/openSUSE:Factory/.python-trustme.new.1899/python-trustme.changes 2021-09-04 22:32:10.371898516 +0200 @@ -1,0 +2,62 @@ +Mon Aug 30 14:18:19 UTC 2021 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 0.9.0 + * Bump types-cryptography from 3.3.3 to 3.3.5 (#342) + * Bump types-pyopenssl from 20.0.4 to 20.0.5 (#343) + * Add type annotations (#341) + * Bump charset-normalizer from 2.0.3 to 2.0.4 (#340) + * Bump sphinx from 4.1.1 to 4.1.2 + * Bump charset-normalizer from 2.0.2 to 2.0.3 + * Bump idna from 2.10 to 3.2 + * Bump sphinx from 4.1.0 to 4.1.1 + * Bump charset-normalizer from 2.0.1 to 2.0.2 + * Bump requests from 2.25.1 to 2.26.0 (#333) + * Bump sphinx from 4.0.2 to 4.1.0 + * Bump urllib3 from 1.26.5 to 1.26.6 + * Bump version to v0.8.0+dev +- from version 0.8.0 + * retry codecov more + * try codecov harder + * require codecov in ci + * Update tests/test_trustme.py + * close the wrapped sockets to prevent Unraisable ResourceWarnings + * Adjust tests + * py3.10 needs a new version of pytest + * Set correct KU and EKU extensions + * test on py 3.10 + * Bump pytest-cov from 2.12.0 to 2.12.1 + * Bump certifi from 2020.12.5 to 2021.5.30 + * Bump urllib3 from 1.26.4 to 1.26.5 + * Bump sphinxcontrib-htmlhelp from 1.0.3 to 2.0.0 + * Bump sphinxcontrib-serializinghtml from 1.1.4 to 1.1.5 + * Bump jinja2 from 2.11.3 to 3.0.1 + * Bump sphinx from 4.0.1 to 4.0.2 + * Bump pytest-cov from 2.11.1 to 2.12.0 + * Bump docutils from 0.16 to 0.17.1 + * Bump sphinx from 4.0.0 to 4.0.1 + * Bump service-identity from 18.1.0 to 21.1.0 + * Bump sphinx from 3.5.4 to 4.0.0 + * Bump attrs from 21.1.0 to 21.2.0 + * Bump attrs from 20.3.0 to 21.1.0 + * Bump six from 1.15.0 to 1.16.0 + * Bump pygments from 2.8.1 to 2.9.0 + * Upgrade to GitHub-native Dependabot + * Bump babel from 2.9.0 to 2.9.1 + * Bump sphinx from 3.5.3 to 3.5.4 + * Bump docutils from 0.16 to 0.17 + * Bump sphinx from 3.5.2 to 3.5.3 + * Mention not_after in `issue_cert` signature + * Bump urllib3 from 1.26.3 to 1.26.4 + * Bump pygments from 2.8.0 to 2.8.1 + * Bump sphinx from 3.5.1 to 3.5.2 + * Add newsfragment and Python doc for --expires-on + * Add an option to set when the certificate should expire (--expires-on) + * Bump coverage from 5.4 to 5.5 + * Bump sphinx from 3.5.0 to 3.5.1 + * Clarify project vision in README + * Bump sphinx from 3.4.3 to 3.5.0 + * Bump pygments from 2.7.4 to 2.8.0 + * Bump cffi from 1.14.4 to 1.14.5 + * Bump version to 0.7.0 + +------------------------------------------------------------------- Old: ---- trustme-0.7.0.tar.gz New: ---- trustme-0.9.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-trustme.spec ++++++ --- /var/tmp/diff_new_pack.6qsB8f/_old 2021-09-04 22:32:10.887899199 +0200 +++ /var/tmp/diff_new_pack.6qsB8f/_new 2021-09-04 22:32:10.887899199 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without python2 Name: python-trustme -Version: 0.7.0 +Version: 0.9.0 Release: 0 Summary: Fake CA provider for Python tests License: Apache-2.0 OR MIT ++++++ trustme-0.7.0.tar.gz -> trustme-0.9.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/PKG-INFO new/trustme-0.9.0/PKG-INFO --- old/trustme-0.7.0/PKG-INFO 2021-02-10 07:58:32.458999200 +0100 +++ new/trustme-0.9.0/PKG-INFO 2021-08-12 22:04:30.397959200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: trustme -Version: 0.7.0 +Version: 0.9.0 Summary: #1 quality TLS certs while you wait, for the discerning tester Home-page: https://github.com/python-trio/trustme Author: Nathaniel J. Smith @@ -128,13 +128,17 @@ work with. Actually easier, in many cases. **What if I want to test how my code handles some bizarre TLS - configuration?** Sure, I'm happy to extend the API to give more - control over the generated certificates, at least as long as it - doesn't turn into a second-rate re-export of everything in - `cryptography <https://cryptography.io>`__. (If you need a - fully general X.509 library, then they do a great job at that.) `Let's - talk <https://github.com/python-trio/trustme/issues/new>`__, or send a - PR. + configuration?** We think trustme hits a sweet spot of ease-of-use + and generality as it is. The defaults are carefully chosen to work + on all major operating systems and be as fast as possible. We don't + want to turn trustme into a second-rate re-export of everything in + `cryptography <https://cryptography.io>`__. If you have more complex + needs, consider using them directly, possibly starting from the + trustme code. + + **Will you automate installing CA cert into system trust store?** No. + `mkcert <https://github.com/FiloSottile/mkcert>`__ already does this + well, and we would not have anything to add. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta @@ -146,10 +150,11 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: System :: Networking Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Testing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/README.rst new/trustme-0.9.0/README.rst --- old/trustme-0.7.0/README.rst 2021-02-10 07:22:45.000000000 +0100 +++ new/trustme-0.9.0/README.rst 2021-03-16 20:25:07.000000000 +0100 @@ -120,10 +120,14 @@ work with. Actually easier, in many cases. **What if I want to test how my code handles some bizarre TLS -configuration?** Sure, I'm happy to extend the API to give more -control over the generated certificates, at least as long as it -doesn't turn into a second-rate re-export of everything in -`cryptography <https://cryptography.io>`__. (If you need a -fully general X.509 library, then they do a great job at that.) `Let's -talk <https://github.com/python-trio/trustme/issues/new>`__, or send a -PR. +configuration?** We think trustme hits a sweet spot of ease-of-use +and generality as it is. The defaults are carefully chosen to work +on all major operating systems and be as fast as possible. We don't +want to turn trustme into a second-rate re-export of everything in +`cryptography <https://cryptography.io>`__. If you have more complex +needs, consider using them directly, possibly starting from the +trustme code. + +**Will you automate installing CA cert into system trust store?** No. +`mkcert <https://github.com/FiloSottile/mkcert>`__ already does this +well, and we would not have anything to add. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/docs/source/index.rst new/trustme-0.9.0/docs/source/index.rst --- old/trustme-0.7.0/docs/source/index.rst 2021-02-10 07:54:11.000000000 +0100 +++ new/trustme-0.9.0/docs/source/index.rst 2021-08-12 21:59:09.000000000 +0200 @@ -119,6 +119,26 @@ .. towncrier release notes start +Trustme 0.9.0 (2021-08-12) +-------------------------- + +Features +~~~~~~~~ + +- The package is now type annotated. If you use mypy on code which uses ``trustme``, you should be able to remove any exclusions. (`#339 <https://github.com/python-trio/trustme/issues/339>`__) + + +Trustme 0.8.0 (2021-06-08) +-------------------------- + +Features +~~~~~~~~ + +- It's now possible to set an expiry date on server certificates, either with ``--expires-on`` in the CLI or with ``not_after`` in `trustme.CA.issue_cert`. (`#293 <https://github.com/python-trio/trustme/issues/293>`__) +- Support Python 3.10 (`#327 <https://github.com/python-trio/trustme/issues/327>`__) +- Set correct KeyUsage and ExtendedKeyUsage extensions, per CA/B Forum baseline requirements. (`#328 <https://github.com/python-trio/trustme/issues/328>`__) + + Trustme 0.7.0 (2021-02-10) ------------------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/pyproject.toml new/trustme-0.9.0/pyproject.toml --- old/trustme-0.7.0/pyproject.toml 2019-12-19 15:04:58.000000000 +0100 +++ new/trustme-0.9.0/pyproject.toml 2021-08-12 21:58:05.000000000 +0200 @@ -17,3 +17,26 @@ underlines = ["-", "~", "^"] # Requires https://github.com/hawkowl/towncrier/pull/66 issue_format = "`#{issue} <https://github.com/python-trio/trustme/issues/{issue}>`__" + +[tool.mypy] +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +no_implicit_reexport = true +show_error_codes = true +strict_equality = true +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true +# Some ignores are only for python2/python3. +# warn_unused_ignores = true + +[[tool.mypy.overrides]] +module = "pytest" +ignore_missing_imports = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/setup.py new/trustme-0.9.0/setup.py --- old/trustme-0.7.0/setup.py 2019-12-19 15:04:58.000000000 +0100 +++ new/trustme-0.9.0/setup.py 2021-08-12 21:58:05.000000000 +0200 @@ -13,6 +13,9 @@ author_email="n...@pobox.com", license="MIT -or- Apache License 2.0", packages=find_packages(), + package_data={ + 'trustme': ['py.typed'], + }, url="https://github.com/python-trio/trustme", install_requires=[ "cryptography", @@ -31,10 +34,11 @@ "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: System :: Networking", "Topic :: Security :: Cryptography", "Topic :: Software Development :: Testing", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/test-requirements.txt new/trustme-0.9.0/test-requirements.txt --- old/trustme-0.7.0/test-requirements.txt 2021-02-10 07:22:45.000000000 +0100 +++ new/trustme-0.9.0/test-requirements.txt 2021-06-08 19:58:20.000000000 +0200 @@ -6,31 +6,53 @@ # atomicwrites==1.4.0 # via pytest -attrs==20.3.0 +attrs==21.2.0 # via # pytest # service-identity -cffi==1.14.4 +backports.functools-lru-cache==1.6.4 + # via wcwidth +cffi==1.14.5 # via cryptography -coverage==5.4 +configparser==4.0.2 + # via importlib-metadata +contextlib2==0.6.0.post1 + # via + # importlib-metadata + # zipp +coverage==5.5 # via pytest-cov cryptography==2.9.2 # via # -r test-requirements.in # pyopenssl # service-identity -futures==3.1.1 +enum34==1.1.10 + # via cryptography +funcsigs==1.0.2 + # via pytest +futures==3.1.1 ; python_version < "3.2" # via -r test-requirements.in -idna==2.10 +idna==2.10 ; python_version < "3" # via -r test-requirements.in importlib-metadata==2.0.0 - # via pytest -more-itertools==5.0.0 + # via + # pluggy + # pytest +ipaddress==1.0.23 + # via + # cryptography + # service-identity +more-itertools==5.0.0 ; python_version < "3" # via # -r test-requirements.in # pytest packaging==20.9 # via pytest +pathlib2==2.3.5 + # via + # importlib-metadata + # pytest pluggy==0.13.1 # via pytest py==1.10.0 @@ -47,23 +69,29 @@ # via -r test-requirements.in pyparsing==2.4.7 # via packaging -pytest-cov==2.11.1 +pytest-cov==2.12.1 # via -r test-requirements.in pytest==4.6.3 # via # -r test-requirements.in # pytest-cov -service-identity==18.1.0 +scandir==1.10.0 + # via pathlib2 +service-identity==21.1.0 # via -r test-requirements.in -six==1.15.0 +six==1.16.0 # via # cryptography # more-itertools + # pathlib2 # pyopenssl # pytest + # service-identity +toml==0.10.2 + # via pytest-cov wcwidth==0.2.5 # via pytest -zipp==1.2.0 +zipp==1.2.0 ; python_version < "3" # via # -r test-requirements.in # importlib-metadata diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/tests/test_cli.py new/trustme-0.9.0/tests/test_cli.py --- old/trustme-0.7.0/tests/test_cli.py 2021-02-10 07:22:45.000000000 +0100 +++ new/trustme-0.9.0/tests/test_cli.py 2021-08-12 21:58:05.000000000 +0200 @@ -3,12 +3,18 @@ import subprocess import sys +import py import pytest from trustme._cli import main +TYPE_CHECKING = False +if TYPE_CHECKING: # pragma: no cover + from typing import Any + def test_trustme_cli(tmpdir): + # type: (py.path.local) -> None with tmpdir.as_cwd(): main(argv=[]) @@ -18,6 +24,7 @@ def test_trustme_cli_e2e(tmpdir): + # type: (py.path.local) -> None with tmpdir.as_cwd(): rv = subprocess.call([sys.executable, "-m", "trustme"]) assert rv == 0 @@ -28,6 +35,7 @@ def test_trustme_cli_directory(tmpdir): + # type: (py.path.local) -> None subdir = tmpdir.mkdir("sub") main(argv=["-d", str(subdir)]) @@ -37,12 +45,14 @@ def test_trustme_cli_directory_does_not_exist(tmpdir): + # type: (py.path.local) -> None notdir = tmpdir.join("notdir") with pytest.raises(ValueError, match="is not a directory"): main(argv=["-d", str(notdir)]) def test_trustme_cli_identities(tmpdir): + # type: (py.path.local) -> None with tmpdir.as_cwd(): main(argv=["-i", "example.org", "www.example.org"]) @@ -52,11 +62,13 @@ def test_trustme_cli_identities_empty(tmpdir): + # type: (py.path.local) -> None with pytest.raises(ValueError, match="at least one identity"): main(argv=["-i"]) def test_trustme_cli_common_name(tmpdir): + # type: (py.path.local) -> None with tmpdir.as_cwd(): main(argv=["--common-name", "localhost"]) @@ -65,7 +77,29 @@ assert tmpdir.join("client.pem").check(exists=1) +def test_trustme_cli_expires_on(tmpdir): + # type: (py.path.local) -> None + with tmpdir.as_cwd(): + main(argv=["--expires-on", "2035-03-01"]) + + assert tmpdir.join("server.key").check(exists=1) + assert tmpdir.join("server.pem").check(exists=1) + assert tmpdir.join("client.pem").check(exists=1) + + +def test_trustme_cli_invalid_expires_on(tmpdir): + # type: (py.path.local) -> None + with tmpdir.as_cwd(): + with pytest.raises(ValueError, match="does not match format"): + main(argv=["--expires-on", "foobar"]) + + assert tmpdir.join("server.key").check(exists=0) + assert tmpdir.join("server.pem").check(exists=0) + assert tmpdir.join("client.pem").check(exists=0) + + def test_trustme_cli_quiet(capsys, tmpdir): + # type: (Any, py.path.local) -> None with tmpdir.as_cwd(): main(argv=["-q"]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/tests/test_trustme.py new/trustme-0.9.0/tests/test_trustme.py --- old/trustme-0.7.0/tests/test_trustme.py 2020-07-26 14:57:03.000000000 +0200 +++ new/trustme-0.9.0/tests/test_trustme.py 2021-08-12 21:58:05.000000000 +0200 @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import py import pytest import sys @@ -7,7 +8,7 @@ import socket import threading import datetime -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor # type: ignore[import] from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network @@ -16,19 +17,27 @@ from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, load_pem_private_key) -import OpenSSL -import service_identity.pyopenssl +import OpenSSL.SSL +import service_identity.pyopenssl # type: ignore[import] import trustme -from trustme import CA +from trustme import CA, LeafCert + +TYPE_CHECKING = False +if TYPE_CHECKING: # pragma: no cover + from typing import Callable, Optional, Text, Union + + SslSocket = Union[ssl.SSLSocket, OpenSSL.SSL.Connection] def _path_length(ca_cert): + # type: (x509.Certificate) -> Optional[int] bc = ca_cert.extensions.get_extension_for_class(x509.BasicConstraints) return bc.value.path_length def assert_is_ca(ca_cert): + # type: (x509.Certificate) -> None bc = ca_cert.extensions.get_extension_for_class(x509.BasicConstraints) assert bc.value.ca is True assert bc.critical is True @@ -38,7 +47,24 @@ assert ku.value.crl_sign is True assert ku.critical is True - eku = ca_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + with pytest.raises(x509.ExtensionNotFound): + ca_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + + +def assert_is_leaf(leaf_cert): + # type: (x509.Certificate) -> None + bc = leaf_cert.extensions.get_extension_for_class(x509.BasicConstraints) + assert bc.value.ca is False + assert bc.critical is True + + ku = leaf_cert.extensions.get_extension_for_class(x509.KeyUsage) + assert ku.value.digital_signature is True + assert ku.value.key_encipherment is True + assert ku.value.key_cert_sign is False + assert ku.value.crl_sign is False + assert ku.critical is True + + eku = leaf_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) assert eku.value == x509.ExtendedKeyUsage([ x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH, x509.oid.ExtendedKeyUsageOID.SERVER_AUTH, @@ -48,6 +74,7 @@ def test_basics(): + # type: () -> None ca = CA() today = datetime.datetime.today() @@ -88,6 +115,7 @@ assert server_cert.not_valid_before <= today <= server_cert.not_valid_after assert server_cert.issuer == ca_cert.subject + assert_is_leaf(server_cert) san = server_cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) hostnames = san.value.get_values_for_type(x509.DNSName) @@ -95,6 +123,7 @@ def test_ca_custom_names(): + # type: () -> None ca = CA( organization_name=u'python-trio', organization_unit_name=u'trustme', @@ -115,6 +144,7 @@ def test_issue_cert_custom_names(): + # type: () -> None ca = CA() leaf_cert = ca.issue_cert( u'example.org', @@ -136,7 +166,30 @@ }) +def test_issue_cert_custom_not_after(): + # type: () -> None + now = datetime.datetime.now() + expires = datetime.datetime(2025, 12, 1, 8, 10, 10) + ca = CA() + + leaf_cert = ca.issue_cert( + u'example.org', + organization_name=u'python-trio', + organization_unit_name=u'trustme', + not_after=expires, + ) + + cert = x509.load_pem_x509_certificate( + leaf_cert.cert_chain_pems[0].bytes(), + default_backend(), + ) + + for t in ["year", "month", "day", "hour", "minute", "second"]: + assert getattr(cert.not_valid_after, t) == getattr(expires, t) + + def test_intermediate(): + # type: () -> None ca = CA() ca_cert = x509.load_pem_x509_certificate( ca.cert_pem.bytes(), default_backend()) @@ -156,9 +209,11 @@ child_server_cert = x509.load_pem_x509_certificate( child_server.cert_chain_pems[0].bytes(), default_backend()) assert child_server_cert.issuer == child_ca_cert.subject + assert_is_leaf(child_server_cert) def test_path_length(): + # type: () -> None ca = CA() ca_cert = x509.load_pem_x509_certificate( ca.cert_pem.bytes(), default_backend()) @@ -177,17 +232,19 @@ def test_unrecognized_context_type(): + # type: () -> None ca = CA() server = ca.issue_cert(u"test-1.example.org") with pytest.raises(TypeError): - ca.configure_trust(None) + ca.configure_trust(None) # type: ignore[arg-type] with pytest.raises(TypeError): - server.configure_cert(None) + server.configure_cert(None) # type: ignore[arg-type] def test_blob(tmpdir): + # type: (py.path.local) -> None test_data = b"xyzzy" b = trustme.Blob(test_data) @@ -223,6 +280,7 @@ assert f.read() == test_data def test_ca_from_pem(tmpdir): + # type: (py.path.local) -> None ca1 = trustme.CA() ca2 = trustme.CA.from_pem(ca1.cert_pem.bytes(), ca1.private_key_pem.bytes()) assert ca1._certificate == ca2._certificate @@ -230,13 +288,16 @@ def check_connection_end_to_end(wrap_client, wrap_server): + # type: (Callable[[CA, socket.socket, Text], SslSocket], Callable[[LeafCert, socket.socket], SslSocket]) -> None # Client side def fake_ssl_client(ca, raw_client_sock, hostname): + # type: (CA, socket.socket, Text) -> None try: wrapped_client_sock = wrap_client(ca, raw_client_sock, hostname) # Send and receive some data to prove the connection is good wrapped_client_sock.send(b"x") assert wrapped_client_sock.recv(1) == b"y" + wrapped_client_sock.close() except: # pragma: no cover sys.excepthook(*sys.exc_info()) raise @@ -245,11 +306,13 @@ # Server side def fake_ssl_server(server_cert, raw_server_sock): + # type: (LeafCert, socket.socket) -> None try: wrapped_server_sock = wrap_server(server_cert, raw_server_sock) # Prove that we're connected assert wrapped_server_sock.recv(1) == b"x" wrapped_server_sock.send(b"y") + wrapped_server_sock.close() except: # pragma: no cover sys.excepthook(*sys.exc_info()) raise @@ -257,6 +320,7 @@ raw_server_sock.close() def doit(ca, hostname, server_cert): + # type: (CA, Text, LeafCert) -> None # socketpair and ssl don't work together on py2, because... reasons. # So we need to do this the hard way. listener = socket.socket() @@ -297,18 +361,23 @@ def test_stdlib_end_to_end(): + # type: () -> None def wrap_client(ca, raw_client_sock, hostname): + # type: (CA, socket.socket, Text) -> ssl.SSLSocket ctx = ssl.create_default_context() ca.configure_trust(ctx) + # Type ignore for Python 2: wants str, got unicode, but I guess unicode also works. wrapped_client_sock = ctx.wrap_socket( - raw_client_sock, server_hostname=hostname) + raw_client_sock, server_hostname=hostname) # type: ignore[arg-type] print("Client got server cert:", wrapped_client_sock.getpeercert()) peercert = wrapped_client_sock.getpeercert() + assert peercert is not None san = peercert["subjectAltName"] assert san == (("DNS", "my-test-host.example.org"),) return wrapped_client_sock def wrap_server(server_cert, raw_server_sock): + # type: (LeafCert, socket.socket) -> ssl.SSLSocket ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) server_cert.configure_cert(ctx) wrapped_server_sock = ctx.wrap_socket( @@ -320,12 +389,14 @@ def test_pyopenssl_end_to_end(): + # type: () -> None def wrap_client(ca, raw_client_sock, hostname): + # type: (CA, socket.socket, Text) -> OpenSSL.SSL.Connection # Cribbed from example at # https://service-identity.readthedocs.io/en/stable/api.html#service_identity.pyopenssl.verify_hostname ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) ctx.set_verify(OpenSSL.SSL.VERIFY_PEER, - lambda conn, cert, errno, depth, ok: ok) + lambda conn, cert, errno, depth, ok: bool(ok)) ca.configure_trust(ctx) conn = OpenSSL.SSL.Connection(ctx, raw_client_sock) conn.set_connect_state() @@ -334,6 +405,7 @@ return conn def wrap_server(server_cert, raw_server_sock): + # type: (LeafCert, socket.socket) -> OpenSSL.SSL.Connection ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) server_cert.configure_cert(ctx) @@ -346,11 +418,12 @@ def test_identity_variants(): + # type: () -> None ca = CA() for bad in [b"example.org", bytearray(b"example.org"), 123]: with pytest.raises(TypeError): - ca.issue_cert(bad) + ca.issue_cert(bad) # type: ignore[arg-type] cases = { # Traditional ascii hostname @@ -401,27 +474,30 @@ san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) - got = san.value[0] + assert_is_leaf(cert) + got = list(san.value)[0] assert got == expected def test_backcompat(): + # type: () -> None ca = CA() # We can still use the old name ca.issue_server_cert(u"example.com") def test_CN(): + # type: () -> None ca = CA() # Since we have to emulate kwonly args here, I guess we should test the # emulation logic with pytest.raises(TypeError): - ca.issue_cert(comon_nam=u"wrong kwarg name") + ca.issue_cert(comon_nam=u"wrong kwarg name") # type: ignore[call-arg] # Must be unicode with pytest.raises(TypeError): - ca.issue_cert(common_name=b"bad kwarg value") + ca.issue_cert(common_name=b"bad kwarg value") # type: ignore[arg-type] # Default is no common name pem = ca.issue_cert(u"example.com").cert_chain_pems[0].bytes() @@ -445,7 +521,7 @@ san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) - assert san.value[0] == x509.DNSName(u"example.com") + assert list(san.value)[0] == x509.DNSName(u"example.com") common_names = cert.subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/trustme/__init__.py new/trustme-0.9.0/trustme/__init__.py --- old/trustme-0.7.0/trustme/__init__.py 2020-07-26 14:57:03.000000000 +0200 +++ new/trustme-0.9.0/trustme/__init__.py 2021-08-12 21:58:05.000000000 +0200 @@ -8,7 +8,7 @@ import os import ipaddress -import idna +import idna # type: ignore[import] from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -23,6 +23,12 @@ from ._version import __version__ +TYPE_CHECKING = False +if TYPE_CHECKING: # pragma: no cover + from typing import Generator, List, Optional, Text, Union + + import OpenSSL.SSL + __all__ = ["CA"] # Python 2/3 annoyingness @@ -37,8 +43,17 @@ # by default reject any keys with <2048 bits (see #45). _KEY_SIZE = 2048 +# Default certificate expiry date: +# OpenSSL on Windows fails if you try to give it a date after +# ~3001-01-19: +# https://github.com/pyca/cryptography/issues/3194 +# Some versions of cryptography on 32-bit platforms fail if you give +# them dates after ~2038-01-19: +# https://github.com/pyca/cryptography/pull/4658 +DEFAULT_EXPIRY = datetime.datetime(2038, 1, 1) def _name(name, organization_name=None, common_name=None): + # type: (Text, Optional[Text], Optional[Text]) -> x509.Name name_pieces = [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, @@ -54,27 +69,25 @@ def random_text(): + # type: () -> Text return urlsafe_b64encode(os.urandom(12)).decode("ascii") def _smells_like_pyopenssl(ctx): - return getattr(ctx, "__module__", "").startswith("OpenSSL") + # type: (object) -> bool + return getattr(ctx, "__module__", "").startswith("OpenSSL") # type: ignore[no-any-return] -def _cert_builder_common(subject, issuer, public_key): +def _cert_builder_common(subject, issuer, public_key, not_after=None): + # type: (x509.Name, x509.Name, rsa.RSAPublicKey, Optional[datetime.datetime]) -> x509.CertificateBuilder + not_after = not_after if not_after else DEFAULT_EXPIRY return ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(public_key) .not_valid_before(datetime.datetime(2000, 1, 1)) - # OpenSSL on Windows fails if you try to give it a date after - # ~3001-01-19: - # https://github.com/pyca/cryptography/issues/3194 - # Some versions of cryptography on 32-bit platforms fail if you give - # them dates after ~2038-01-19: - # https://github.com/pyca/cryptography/pull/4658 - .not_valid_after(datetime.datetime(2038, 1, 1)) + .not_valid_after(not_after) .serial_number(x509.random_serial_number()) .add_extension( x509.SubjectKeyIdentifier.from_public_key(public_key), @@ -84,6 +97,7 @@ def _identity_string_to_x509(identity): + # type: (Text) -> x509.GeneralName # Because we are a DWIM library for lazy slackers, we cheerfully pervert # the cryptography library's carefully type-safe API, and silently DTRT # for any of the following identity types: @@ -109,13 +123,13 @@ # Have to try ip_address first, because ip_network("127.0.0.1") is # interpreted as being the network 127.0.0.1/32. Which I guess would be # fine, actually, but why risk it. - for ip_converter in [ipaddress.ip_address, ipaddress.ip_network]: + try: + return x509.IPAddress(ipaddress.ip_address(identity)) + except ValueError: try: - ip_hostname = ip_converter(identity) + return x509.IPAddress(ipaddress.ip_network(identity)) except ValueError: - continue - else: - return x509.IPAddress(ip_hostname) + pass # Encode to an A-label, like cryptography wants if identity.startswith("*."): @@ -137,15 +151,18 @@ """ def __init__(self, data): + # type: (bytes) -> None self._data = data def bytes(self): + # type: () -> bytes """Returns the data as a `bytes` object. """ return self._data def write_to_path(self, path, append=False): + # type: (str, bool) -> None """Writes the data to the file at the given path. Args: @@ -163,6 +180,7 @@ @contextmanager def tempfile(self, dir=None): + # type: (Optional[str]) -> Generator[str, None, None] """Context manager for writing data to a temporary file. The file is created when you enter the context manager, and @@ -192,7 +210,9 @@ # open. Which seems like it completely defeats the purpose of having a # NamedTemporaryFile? Oh well... # https://bugs.python.org/issue14243 - f = NamedTemporaryFile(suffix=".pem", dir=dir, delete=False) + # Type ignore temporarily needed for Python 2: + # https://github.com/python/typeshed/pull/5836 + f = NamedTemporaryFile(suffix=".pem", dir=dir, delete=False) # type: ignore[arg-type] try: f.write(self._data) f.close() @@ -204,6 +224,9 @@ class CA(object): """A certificate authority.""" + + _certificate = None # type: x509.Certificate + def __init__( self, parent_cert=None, @@ -211,6 +234,7 @@ organization_name=None, organization_unit_name=None, ): + # type: (Optional[CA], int, Optional[Text], Optional[Text]) -> None self.parent_cert = parent_cert self._private_key = rsa.generate_private_key( public_exponent=65537, @@ -225,9 +249,10 @@ ) issuer = name sign_key = self._private_key - if self.parent_cert is not None: + if parent_cert is not None: sign_key = parent_cert._private_key - issuer = parent_cert._certificate.subject + parent_certificate = parent_cert._certificate + issuer = parent_certificate.subject self._certificate = ( _cert_builder_common(name, issuer, self._private_key.public_key()) @@ -237,25 +262,17 @@ ) .add_extension( x509.KeyUsage( - digital_signature=False, + digital_signature=True, # OCSP content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, - key_cert_sign=True, - crl_sign=True, + key_cert_sign=True, # sign certs + crl_sign=True, # sign revocation lists encipher_only=False, decipher_only=False), critical=True ) - .add_extension( - x509.ExtendedKeyUsage([ - ExtendedKeyUsageOID.CLIENT_AUTH, - ExtendedKeyUsageOID.SERVER_AUTH, - ExtendedKeyUsageOID.CODE_SIGNING, - ]), - critical=True - ) .sign( private_key=sign_key, algorithm=hashes.SHA256(), @@ -265,12 +282,14 @@ @property def cert_pem(self): + # type: () -> Blob """`Blob`: The PEM-encoded certificate for this CA. Add this to your trust store to trust this CA.""" return Blob(self._certificate.public_bytes(Encoding.PEM)) @property def private_key_pem(self): + # type: () -> Blob """`Blob`: The PEM-encoded private key for this CA. Use this to sign other certificates from this CA.""" return Blob( @@ -282,6 +301,7 @@ ) def create_child_ca(self): + # type: () -> CA """Creates a child certificate authority Returns: @@ -297,8 +317,10 @@ return CA(parent_cert=self, path_length=path_length) def issue_cert(self, *identities, **kwargs): + # type: (Text, Optional[Union[Text, datetime.datetime]]) -> LeafCert + # PY3: (str, Optional[str], Optional[str], Optional[str], Optional[datetime.datetime]) -> LeafCert """issue_cert(*identities, common_name=None, organization_name=None, \ - organization_unit_name=None) + organization_unit_name=None, not_after=None) Issues a certificate. The certificate can be used for either servers or clients. @@ -340,13 +362,17 @@ attribute on the certificate. By default, a random one will be generated. + not_after: Set the expiry date (notAfter) of the certificate. This + argument type is `datetime.datetime`. + Returns: LeafCert: the newly-generated certificate. """ - common_name = kwargs.pop("common_name", None) - organization_name = kwargs.pop("organization_name", None) - organization_unit_name = kwargs.pop("organization_unit_name", None) + common_name = kwargs.pop("common_name", None) # type: Optional[Text] # type: ignore[assignment] + organization_name = kwargs.pop("organization_name", None) # type: Optional[Text] # type: ignore[assignment] + organization_unit_name = kwargs.pop("organization_unit_name", None) # type: Optional[Text] # type: ignore[assignment] + not_after = kwargs.pop("not_after", None) # type: Optional[datetime.datetime] # type: ignore[assignment] if kwargs: raise TypeError("unrecognized keyword arguments {}".format(kwargs)) @@ -371,7 +397,7 @@ aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ski) except AttributeError: # The old way - aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ski_ext) + aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ski_ext) # type: ignore[arg-type] cert = ( _cert_builder_common( @@ -382,6 +408,7 @@ ), self._certificate.subject, key.public_key(), + not_after=not_after, ) .add_extension( x509.BasicConstraints(ca=False, path_length=None), @@ -394,6 +421,27 @@ ), critical=True, ) + .add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=True, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False), + critical=True + ) + .add_extension( + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]), + critical=True + ) .sign( private_key=self._private_key, algorithm=hashes.SHA256(), @@ -421,6 +469,7 @@ issue_server_cert = issue_cert def configure_trust(self, ctx): + # type: (Union[ssl.SSLContext, OpenSSL.SSL.Context]) -> None """Configure the given context object to trust certificates signed by this CA. @@ -445,6 +494,7 @@ @classmethod def from_pem(cls, cert_bytes, private_key_bytes): + # type: (bytes, bytes) -> CA """Build a CA from existing cert and private key. This is useful if your test suite has an existing certificate authority and @@ -483,6 +533,7 @@ """ def __init__(self, private_key_pem, server_cert_pem, chain_to_ca): + # type: (bytes, bytes, List[bytes]) -> None self.private_key_pem = Blob(private_key_pem) self.cert_chain_pems = [ Blob(pem) for pem in [server_cert_pem] + chain_to_ca] @@ -490,6 +541,7 @@ Blob(private_key_pem + server_cert_pem + b''.join(chain_to_ca))) def configure_cert(self, ctx): + # type: (Union[ssl.SSLContext, OpenSSL.SSL.Context]) -> None """Configure the given context object to present this certificate. Args: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/trustme/_cli.py new/trustme-0.9.0/trustme/_cli.py --- old/trustme-0.7.0/trustme/_cli.py 2021-02-10 07:22:45.000000000 +0100 +++ new/trustme-0.9.0/trustme/_cli.py 2021-08-12 21:58:05.000000000 +0200 @@ -5,14 +5,23 @@ import trustme import sys +from datetime import datetime + +TYPE_CHECKING = False +if TYPE_CHECKING: # pragma: no cover + from typing import List, Optional + # Python 2/3 annoyingness try: unicode except NameError: # pragma: no cover unicode = str +# ISO 8601 +DATE_FORMAT = '%Y-%m-%d' def main(argv=None): + # type: (Optional[List[str]]) -> None if argv is None: argv = sys.argv[1:] @@ -37,6 +46,13 @@ help="Also sets the deprecated 'commonName' field (only for the first identity passed).", ) parser.add_argument( + "-x", + "--expires-on", + default=None, + help="Set the date the certificate will expire on (in YYYY-MM-DD format).", + metavar='YYYY-MM-DD', + ) + parser.add_argument( "-q", "--quiet", action="store_true", @@ -47,6 +63,7 @@ cert_dir = args.dir identities = [unicode(identity) for identity in args.identities] common_name = unicode(args.common_name[0]) if args.common_name else None + expires_on = None if args.expires_on is None else datetime.strptime(args.expires_on, DATE_FORMAT) quiet = args.quiet if not os.path.isdir(cert_dir): @@ -56,7 +73,7 @@ # Generate the CA certificate ca = trustme.CA() - cert = ca.issue_cert(*identities, common_name=common_name) + cert = ca.issue_cert(*identities, common_name=common_name, not_after=expires_on) # Write the certificate and private key the server should use server_key = os.path.join(cert_dir, "server.key") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/trustme/_version.py new/trustme-0.9.0/trustme/_version.py --- old/trustme-0.7.0/trustme/_version.py 2021-02-10 07:54:32.000000000 +0100 +++ new/trustme-0.9.0/trustme/_version.py 2021-08-12 21:59:03.000000000 +0200 @@ -1 +1 @@ -__version__ = "0.7.0" +__version__ = "0.9.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/trustme.egg-info/PKG-INFO new/trustme-0.9.0/trustme.egg-info/PKG-INFO --- old/trustme-0.7.0/trustme.egg-info/PKG-INFO 2021-02-10 07:58:31.000000000 +0100 +++ new/trustme-0.9.0/trustme.egg-info/PKG-INFO 2021-08-12 22:04:29.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: trustme -Version: 0.7.0 +Version: 0.9.0 Summary: #1 quality TLS certs while you wait, for the discerning tester Home-page: https://github.com/python-trio/trustme Author: Nathaniel J. Smith @@ -128,13 +128,17 @@ work with. Actually easier, in many cases. **What if I want to test how my code handles some bizarre TLS - configuration?** Sure, I'm happy to extend the API to give more - control over the generated certificates, at least as long as it - doesn't turn into a second-rate re-export of everything in - `cryptography <https://cryptography.io>`__. (If you need a - fully general X.509 library, then they do a great job at that.) `Let's - talk <https://github.com/python-trio/trustme/issues/new>`__, or send a - PR. + configuration?** We think trustme hits a sweet spot of ease-of-use + and generality as it is. The defaults are carefully chosen to work + on all major operating systems and be as fast as possible. We don't + want to turn trustme into a second-rate re-export of everything in + `cryptography <https://cryptography.io>`__. If you have more complex + needs, consider using them directly, possibly starting from the + trustme code. + + **Will you automate installing CA cert into system trust store?** No. + `mkcert <https://github.com/FiloSottile/mkcert>`__ already does this + well, and we would not have anything to add. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta @@ -146,10 +150,11 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: System :: Networking Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Testing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trustme-0.7.0/trustme.egg-info/SOURCES.txt new/trustme-0.9.0/trustme.egg-info/SOURCES.txt --- old/trustme-0.7.0/trustme.egg-info/SOURCES.txt 2021-02-10 07:58:31.000000000 +0100 +++ new/trustme-0.9.0/trustme.egg-info/SOURCES.txt 2021-08-12 22:04:29.000000000 +0200 @@ -21,6 +21,7 @@ trustme/__main__.py trustme/_cli.py trustme/_version.py +trustme/py.typed trustme.egg-info/PKG-INFO trustme.egg-info/SOURCES.txt trustme.egg-info/dependency_links.txt