def client_context(ca: CA) -> SSLContext: client_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): client_context.options ^= ssl.OP_IGNORE_UNEXPECTED_EOF # type: ignore[attr-defined] ca.configure_trust(client_context) return client_context
def server_context(ca: CA) -> SSLContext: server_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): server_context.options ^= ssl.OP_IGNORE_UNEXPECTED_EOF # type: ignore[attr-defined] ca.issue_cert("localhost").configure_cert(server_context) return server_context
def test_identity_variants(): ca = CA() for bad in [b"example.org", bytearray(b"example.org"), 123]: with pytest.raises(TypeError): ca.issue_cert(bad) cases = { # Traditional ascii hostname u"example.org": x509.DNSName(u"example.org"), # Wildcard u"*.example.org": x509.DNSName(u"*.example.org"), # IDN u"éxamplë.org": x509.DNSName(u"xn--xampl-9rat.org"), u"xn--xampl-9rat.org": x509.DNSName(u"xn--xampl-9rat.org"), # IDN + wildcard u"*.éxamplë.org": x509.DNSName(u"*.xn--xampl-9rat.org"), u"*.xn--xampl-9rat.org": x509.DNSName(u"*.xn--xampl-9rat.org"), # IDN that acts differently in IDNA-2003 vs IDNA-2008 u"faß.de": x509.DNSName(u"xn--fa-hia.de"), u"xn--fa-hia.de": x509.DNSName(u"xn--fa-hia.de"), # IDN with non-permissable character (uppercase K) # (example taken from idna package docs) u"Königsgäßchen.de": x509.DNSName(u"xn--knigsgchen-b4a3dun.de"), # IP addresses u"127.0.0.1": x509.IPAddress(IPv4Address(u"127.0.0.1")), u"::1": x509.IPAddress(IPv6Address(u"::1")), # Check normalization u"0000::1": x509.IPAddress(IPv6Address(u"::1")), # IP networks u"127.0.0.0/24": x509.IPAddress(IPv4Network(u"127.0.0.0/24")), u"2001::/16": x509.IPAddress(IPv6Network(u"2001::/16")), # Check normalization u"2001:0000::/16": x509.IPAddress(IPv6Network(u"2001::/16")), # Email address u"*****@*****.**": x509.RFC822Name(u"*****@*****.**"), } for hostname, expected in cases.items(): # Can't repr the got or expected values here, at least until # cryptography v2.1 is out, because in v2.0 on py2, DNSName.__repr__ # blows up on IDNs. print("testing: {!r}".format(hostname)) pem = ca.issue_cert(hostname).cert_chain_pems[0].bytes() cert = x509.load_pem_x509_certificate(pem, default_backend()) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert_is_leaf(cert) got = san.value[0] assert got == expected
def test_unrecognized_context_type(): ca = CA() server = ca.issue_cert(u"test-1.example.org") with pytest.raises(TypeError): ca.configure_trust(None) with pytest.raises(TypeError): server.configure_cert(None)
def test_CN(): 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") # Must be unicode with pytest.raises(TypeError): ca.issue_cert(common_name=b"bad kwarg value") # Default is no common name pem = ca.issue_cert(u"example.com").cert_chain_pems[0].bytes() cert = x509.load_pem_x509_certificate(pem, default_backend()) common_names = cert.subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME) assert common_names == [] # Common name on its own is valid pem = ca.issue_cert(common_name=u"woo").cert_chain_pems[0].bytes() cert = x509.load_pem_x509_certificate(pem, default_backend()) common_names = cert.subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME) assert common_names[0].value == u"woo" # Common name + SAN pem = ca.issue_cert(u"example.com", common_name=u"woo").cert_chain_pems[0].bytes() cert = x509.load_pem_x509_certificate(pem, default_backend()) san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) assert san.value[0] == x509.DNSName(u"example.com") common_names = cert.subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME) assert common_names[0].value == u"woo"
def test_basics(): ca = CA() today = datetime.datetime.today() assert b"BEGIN RSA PRIVATE KEY" in ca.private_key_pem.bytes() assert b"BEGIN CERTIFICATE" in ca.cert_pem.bytes() private_key = load_pem_private_key(ca.private_key_pem.bytes(), password=None, backend=default_backend()) ca_cert = x509.load_pem_x509_certificate(ca.cert_pem.bytes(), default_backend()) assert ca_cert.not_valid_before <= today <= ca_cert.not_valid_after public_key1 = private_key.public_key().public_bytes( Encoding.PEM, PublicFormat.PKCS1) public_key2 = ca_cert.public_key().public_bytes(Encoding.PEM, PublicFormat.PKCS1) assert public_key1 == public_key2 assert ca_cert.issuer == ca_cert.subject assert_is_ca(ca_cert) with pytest.raises(ValueError): ca.issue_cert() server = ca.issue_cert(u"test-1.example.org", u"test-2.example.org") assert b"PRIVATE KEY" in server.private_key_pem.bytes() assert b"BEGIN CERTIFICATE" in server.cert_chain_pems[0].bytes() assert len(server.cert_chain_pems) == 1 assert server.private_key_pem.bytes( ) in server.private_key_and_cert_chain_pem.bytes() for blob in server.cert_chain_pems: assert blob.bytes() in server.private_key_and_cert_chain_pem.bytes() server_cert = x509.load_pem_x509_certificate( server.cert_chain_pems[0].bytes(), default_backend()) assert server_cert.not_valid_before <= today <= server_cert.not_valid_after assert server_cert.issuer == ca_cert.subject san = server_cert.extensions.get_extension_for_class( x509.SubjectAlternativeName) hostnames = san.value.get_values_for_type(x509.DNSName) assert hostnames == [u"test-1.example.org", u"test-2.example.org"]
def test_issue_cert_custom_names(): ca = CA() leaf_cert = ca.issue_cert( u'example.org', organization_name=u'python-trio', organization_unit_name=u'trustme', ) cert = x509.load_pem_x509_certificate( leaf_cert.cert_chain_pems[0].bytes(), default_backend(), ) assert { 'O=python-trio', 'OU=trustme', }.issubset({rdn.rfc4514_string() for rdn in cert.subject.rdns})
async def test_send_eof_not_implemented(self, server_context: ssl.SSLContext, ca: CA, force_tlsv12: bool) -> None: def serve_sync() -> None: conn, addr = server_sock.accept() conn.sendall(b"hello") conn.unwrap() conn.close() client_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) ca.configure_trust(client_context) if force_tlsv12: expected_pattern = r"send_eof\(\) requires at least TLSv1.3" if hasattr(ssl, "TLSVersion"): client_context.maximum_version = ssl.TLSVersion.TLSv1_2 else: # Python 3.6 client_context.options |= ssl.OP_NO_TLSv1_3 else: expected_pattern = ( r"send_eof\(\) has not yet been implemented for TLS streams") server_sock = server_context.wrap_socket(socket.socket(), server_side=True, suppress_ragged_eofs=False) server_sock.settimeout(1) server_sock.bind(("127.0.0.1", 0)) server_sock.listen() server_thread = Thread(target=serve_sync, daemon=True) server_thread.start() stream = await connect_tcp(*server_sock.getsockname()) async with await TLSStream.wrap(stream, hostname="localhost", ssl_context=client_context) as wrapper: assert await wrapper.receive() == b"hello" with pytest.raises(NotImplementedError) as exc: await wrapper.send_eof() exc.match(expected_pattern) server_thread.join() server_sock.close()
def test_issue_cert_custom_not_after(): 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(): ca = CA() ca_cert = x509.load_pem_x509_certificate(ca.cert_pem.bytes(), default_backend()) assert_is_ca(ca_cert) assert ca_cert.issuer == ca_cert.subject assert _path_length(ca_cert) == 9 child_ca = ca.create_child_ca() child_ca_cert = x509.load_pem_x509_certificate(child_ca.cert_pem.bytes(), default_backend()) assert_is_ca(child_ca_cert) assert child_ca_cert.issuer == ca_cert.subject assert _path_length(child_ca_cert) == 8 child_server = child_ca.issue_cert(u"test-host.example.org") assert len(child_server.cert_chain_pems) == 2 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
def test_ca_custom_names(): ca = CA( organization_name=u'python-trio', organization_unit_name=u'trustme', ) ca_cert = x509.load_pem_x509_certificate( ca.cert_pem.bytes(), default_backend(), ) assert { 'O=python-trio', 'OU=trustme', }.issubset({rdn.rfc4514_string() for rdn in ca_cert.subject.rdns})
def test_path_length(): ca = CA() ca_cert = x509.load_pem_x509_certificate(ca.cert_pem.bytes(), default_backend()) assert _path_length(ca_cert) == 9 child_ca = ca for i in range(9): child_ca = child_ca.create_child_ca() # Can't create new child CAs anymore child_ca_cert = x509.load_pem_x509_certificate(child_ca.cert_pem.bytes(), default_backend()) assert _path_length(child_ca_cert) == 0 with pytest.raises(ValueError): child_ca.create_child_ca()
def client_ssl_ctx(tls_certificate_authority: trustme.CA) -> ssl.SSLContext: ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) tls_certificate_authority.configure_trust(ssl_ctx) return ssl_ctx
def test_backcompat(): ca = CA() # We can still use the old name ca.issue_server_cert(u"example.com")
def check_connection_end_to_end(wrap_client, wrap_server): # Client side def fake_ssl_client(ca, raw_client_sock, hostname): 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" except: # pragma: no cover sys.excepthook(*sys.exc_info()) raise finally: raw_client_sock.close() # Server side def fake_ssl_server(server_cert, raw_server_sock): 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") except: # pragma: no cover sys.excepthook(*sys.exc_info()) raise finally: raw_server_sock.close() def doit(ca, hostname, server_cert): # socketpair and ssl don't work together on py2, because... reasons. # So we need to do this the hard way. listener = socket.socket() listener.bind(("127.0.0.1", 0)) listener.listen(1) raw_client_sock = socket.socket() raw_client_sock.connect(listener.getsockname()) raw_server_sock, _ = listener.accept() listener.close() with ThreadPoolExecutor(2) as tpe: f1 = tpe.submit(fake_ssl_client, ca, raw_client_sock, hostname) f2 = tpe.submit(fake_ssl_server, server_cert, raw_server_sock) f1.result() f2.result() ca = CA() intermediate_ca = ca.create_child_ca() hostname = u"my-test-host.example.org" # Should work doit(ca, hostname, ca.issue_cert(hostname)) # Should work doit(ca, hostname, intermediate_ca.issue_cert(hostname)) # To make sure that the above success actually required that the # CA and cert logic is all working, make sure that the same code # fails if the certs or CA aren't right: # Bad hostname fails with pytest.raises(Exception): doit(ca, u"asdf.example.org", ca.issue_cert(hostname)) # Bad CA fails bad_ca = CA() with pytest.raises(Exception): doit(bad_ca, hostname, ca.issue_cert(hostname))
def ca_ssl_context(cert_authority: trustme.CA) -> ssl.SSLContext: ctx = ssl.create_default_context() cert_authority.configure_trust(ctx) return ctx
def example_org_cert(cert_authority: trustme.CA) -> trustme.LeafCert: return cert_authority.issue_cert("example.org")
def tls_certificate(tls_certificate_authority: trustme.CA) -> trustme.LeafCert: return tls_certificate_authority.issue_server_cert( "localhost", "127.0.0.1", "::1", )
def localhost_cert(cert_authority: trustme.CA) -> trustme.LeafCert: return cert_authority.issue_cert("localhost")