Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CHANGE EVERYTHING #4

Merged
merged 15 commits into from
Aug 2, 2017
Merged
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@ python:
sudo: false
dist: trusty

matrix:
include:
- os: linux
language: python
python: 3.6
env: DOC_BUILD=1

script:
- ci/travis.sh
196 changes: 71 additions & 125 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
trustme: #1 quality TLS certs while you wait
============================================
.. note that this README gets 'include'ed into the main documentation

==============================================
trustme: #1 quality TLS certs while you wait
==============================================

.. image:: https://vignette2.wikia.nocookie.net/jadensadventures/images/1/1e/Kaa%27s_hypnotic_eyes.jpg/revision/latest?cb=20140310173415
:width: 200px
:align: right

You wrote a cool network client or server. You encrypt your
connections using `TLS
You wrote a cool network client or server. It encrypts connections
using `TLS
<https://en.wikipedia.org/wiki/Transport_Layer_Security>`__. Your test
suite needs to make TLS connections.
suite needs to make TLS connections to itself.

Uh oh. Your test suite *probably* doesn't have a valid TLS
certificate. Now what?
Expand All @@ -21,95 +24,65 @@ just signed by your CA, which nobody trusts. But you can trust
it. Trust me.


Example
=======
Vital statistics
================

**Install:** ``pip install -U trustme``

**Documentation:** https://trustme.readthedocs.io

**Bug tracker and source code:** https://github.com/python-trio/trustme

**Tested on:** Python 2.7 and Python 3.5+, CPython and PyPy

**License:** MIT or Apache 2, your choice.

**Code of conduct:** Contributors are requested to follow our `code of
conduct
<https://github.com/python-trio/trustme/blob/master/CODE_OF_CONDUCT.md>`__
in all project spaces.


Cheat sheet
===========

.. code-block:: python

from trustme import CA
import trustme

# Look, you just became a certificate authority
ca = CA()
# ----- Creating certs -----

# Issue a server cert, signed by your fake CA
# Look, you just created your own certificate authority!
ca = trustme.CA()

# And now you issued a cert signed by this fake CA
# https://en.wikipedia.org/wiki/Example.org
server_cert = ca.issue_server_cert(u"my-test-host.example.org")

# That's it! You have your certs. Now let's see how to use them.

###########

# The simplest thing to do is to take the raw PEM certificates, and
# write them out to some files. Maybe this is useful if you want to
# use them for a test suite written in some other language.

with open("fake-ca.pem", "wb") as f:
f.write(ca.cert_pem)
with open("fake-server-private-key-and-cert-chain.pem", "wb") as f:
f.write(server_cert.private_key_and_cert_chain_pem)

###########

# Or, you can use them directly, for example to make a within-process
# connection between two threads.

import ssl, socket, threading

# Client side
def fake_ssl_client(raw_client_sock):
# Get an ssl.SSLContext object configured to trust your CA
ssl_ctx = ca.stdlib_client_context()
wrapped_client_sock = ssl_ctx.wrap_socket(
raw_client_sock, server_hostname="my-test-host.example.org")
# Look, here's the cert presented by the server
print("Client got server cert:", wrapped_client_sock.getpeercert())
# Send some data to prove the connection is good
wrapped_client_sock.send(b"x")

# Server side
def fake_ssl_server(raw_server_sock):
# Get an ssl.SSLContext object configured to use your server cert
ssl_ctx = server_cert.stdlib_server_context()
wrapped_server_sock = ssl_ctx.wrap_socket(raw_server_sock, server_side=True)
# Prove that we're connected
print("server encrypted with:", wrapped_server_sock.cipher())
assert wrapped_server_sock.recv(1) == b"x"

# Blah blah blah actually run the things
raw_client_sock, raw_server_sock = socket.socketpair()
client_thread = threading.Thread(target=fake_ssl_client, args=(raw_client_sock,))
server_thread = threading.Thread(target=fake_ssl_server, args=(raw_server_sock,))
client_thread.start()
server_thread.start()
client_thread.join()
server_thread.join()


Docs
====

``CA()`` gives you a certificate authority. It has attributes
``.cert_pem`` which is a bytestring containing what it sounds like,
``.issue_server_cert(hostname1, [hostname2, ...])`` which does what it
says on the tin, and ``.stdlib_client_context()``, which is a
convenience method that returns an ``ssl.SSLContext`` object
preconfigured to trust this CA.

``CA.issue_server_cert`` returns a ``ServerCert`` object, which has
attributes ``.private_key_pem``, ``.cert_chain_pem``, and
``.private_key_and_cert_chain_pem``, which are bytestrings containing
what they sound like. It also has a convenience method
``.stdlib_server_context()`` which returns an ``ssl.SSLContext``
object preconfigured to present this cert to any client that
connects.

The ``.stdlib_*_context`` methods accept ``**kwargs``, which are
passed on to `ssl.create_default_context
<https://docs.python.org/3/library/ssl.html#ssl.create_default_context>`__.

Probably this should get moved into Sphinx or something but whatever,
hopefully you get the idea. Or feel free to send a PR converting this
into proper docs.
server_cert = ca.issue_server_cert(u"test-host.example.org")

# That's it!

# ----- Using your shiny new certs -----

# You can configure SSL context objects to trust this CA:
ca.configure_trust(ssl_context)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

This is clear if less pithy than the alternatives we discussed.

# Or configure them to present the server certificate
server_cert.configure_cert(ssl_context)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

# You can use standard library or PyOpenSSL context objects here,
# trustme is happy either way.

# ----- or -----

# Save the PEM-encoded data to a file to use in non-Python test
# suites:
ca.cert_pem.write_to_path("ca.pem")
server_cert.private_key_and_cert_chain_pem.write_to_path("server.pem")

# ----- or -----

# Put the PEM-encoded data in a temporary file, for libraries that
# insist on that:
with ca.cert_pem.tempfile() as ca_temp_path:
requests.get(..., verify=ca_temp_path)


FAQ
Expand All @@ -124,40 +97,13 @@ run in production, and you would *never* disable your certificate
validation code in production, right? Plus they're just as easy to
work with. Maybe easier.

**Why do you only have convenience methods for the stdlib ssl module,
and not PyOpenSSL / Twisted / ...?** Because you didn't send me a PR
yet.

**I want to test some weirdo TLS configuration.** I'm happy to accept
PRs to do simple things like override the default validity period or
set key sizes or whatever, within reason. But if you have complicated
needs then you're probably better offer stealing the code from this
library and adapting it to do what you want. The underlying API is
pretty straightforward. This is just a convenience library for those
of us who need a cheat sheet to tie our shoelaces, X.509-wise.


Vital statistics
================

**Bug tracker and source code:** https://github.com/python-trio/trustme

**License:** MIT or Apache 2, your choice.

**Install:** ``pip install -U trustme``

**Code of conduct:** Contributors are requested to follow our `code of
conduct
<https://github.com/python-trio/trustme/blob/master/CODE_OF_CONDUCT.md>`__
in all project spaces.


Acknowledgements
================

This is basically just a trivial wrapper around the awesome Python
`cryptography <https://cryptography.io/>`__ library. Also, `Glyph
<https://glyph.twistedmatrix.com/>`__ wrote most of the tricky bits. I
got tired of never being able to remember how this works or find the
magic snippets to copy/paste, so I stole the code out of `Twisted
<http://twistedmatrix.com/>`__ and wrapped it in a bow.
**What if I want to test some weirdo TLS configuration?** I'm happy to
accept PRs to do simple things like override the default validity
period or set key sizes or whatever, within reason. If you have
complicated needs though then at some point you're probably better
offer stealing the code from this library and adapting it to do what
you want. The underlying `cryptography <https://cryptography.io>`__
API is pretty straightforward, if what you want to do is create
arbitrary certificate setups. This is largely a convenience library
for those of us who need a cheat sheet to tie our shoelaces,
X.509-wise.
1 change: 1 addition & 0 deletions ci/rtd-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sphinxcontrib_trio
24 changes: 17 additions & 7 deletions ci/travis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ pip install -U pip setuptools wheel
python setup.py sdist --formats=zip
pip install dist/*.zip

pip install -Ur test-requirements.txt
mkdir empty
pushd empty
INSTALLDIR=$(python -c "import os, trustme; print(os.path.dirname(trustme.__file__))")
pytest -W error -ra -s ../tests --cov="$INSTALLDIR" --cov=../tests --cov-config="../.coveragerc"
# ${FOO:-} means "$FOO if defined, else empty string"
if [ "${DOC_BUILD:-}" = "1" ]; then
pip install -U sphinx
pip install -U -r ci/rtd-requirements.txt
cd docs
sphinx-build -nW -b html source build
else
# Actual tests

pip install codecov
codecov
pip install -Ur test-requirements.txt
mkdir empty
pushd empty
INSTALLDIR=$(python -c "import os, trustme; print(os.path.dirname(trustme.__file__))")
pytest -W error -ra -s ../tests --cov="$INSTALLDIR" --cov=../tests --cov-config="../.coveragerc"

pip install codecov
codecov
fi
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python -msphinx
SPHINXPROJ = trustme
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
36 changes: 36 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=python -msphinx
)
set SOURCEDIR=source
set BUILDDIR=build
set SPHINXPROJ=trustme

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
echo.then set the SPHINXBUILD environment variable to point to the full
echo.path of the 'sphinx-build' executable. Alternatively you may add the
echo.Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%

:end
popd
Empty file added docs/source/_static/.gitkeep
Empty file.