├── test_requirements.txt ├── MANIFEST.in ├── .gitmodules ├── .gitignore ├── src └── certitude │ ├── __init__.py │ ├── _certitude.py │ ├── certitude.py │ ├── build.py │ └── api.py ├── tox.ini ├── .travis ├── run.sh └── install.sh ├── README.rst ├── docs ├── source │ ├── installation.rst │ ├── index.rst │ ├── using-certitude.rst │ └── conf.py ├── make.bat └── Makefile ├── LICENSE ├── .travis.yml ├── test ├── fixtures │ ├── selfsigned-badssl.pem │ ├── certifi_chain.pem │ ├── expired-badssl.pem │ └── wronghost-badssl.pem ├── test_basic.py └── conftest.py ├── tasks.py ├── setup.py ├── appveyor └── build.bat ├── appveyor.yml └── rust_ext.py /test_requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==3.4.2 2 | cryptography==2.1.4 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE rust_ext.py 2 | recursive-include src/rust-certitude *.rs *.toml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/rust-certitude"] 2 | path = src/rust-certitude 3 | url = git://github.com/Lukasa/rust-certitude.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | env/ 3 | dist/ 4 | *.egg-info/ 5 | *.pyc 6 | __pycache__ 7 | .coverage 8 | .tox/ 9 | .hypothesis/ 10 | .cache/ 11 | -------------------------------------------------------------------------------- /src/certitude/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | The certitude public API. 4 | 5 | This module exposes only the platform-specific parts of certitude. 6 | """ 7 | from .api import validate_cert_chain 8 | 9 | __all__ = ['validate_cert_chain'] 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py33, py34, py35, pypy, lint 3 | 4 | [testenv] 5 | deps= -r{toxinidir}/test_requirements.txt 6 | commands= py.test {toxinidir}/test/ 7 | 8 | [testenv:lint] 9 | basepython=python3.4 10 | deps = flake8==3.5.0 11 | commands = flake8 --max-complexity 10 src/certitude test 12 | -------------------------------------------------------------------------------- /src/certitude/_certitude.py: -------------------------------------------------------------------------------- 1 | # auto-generated file 2 | import _cffi_backend 3 | 4 | ffi = _cffi_backend.FFI('certitude._certitude', 5 | _version = 0x2601, 6 | _types = b'\x00\x00\x07\x0D\x00\x00\x08\x03\x00\x00\x03\x03\x00\x00\x1C\x01\x00\x00\x06\x03\x00\x00\x00\x0F\x00\x00\x02\x01\x00\x00\x16\x01\x00\x00\x09\x03\x00\x00\x12\x01', 7 | _globals = (b'\x00\x00\x00\x23validate_cert_chain',0,), 8 | ) 9 | -------------------------------------------------------------------------------- /src/certitude/certitude.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import glob 3 | import os 4 | import sys 5 | 6 | from ._certitude import ffi 7 | 8 | if sys.platform == "win32": 9 | extension = ".pyd" # Really? 10 | else: 11 | extension = ".so" 12 | 13 | current_dir = os.path.abspath(os.path.dirname(__file__)) 14 | lib_path = glob.glob(os.path.join(current_dir, "*%s" % extension)) 15 | lib = ffi.dlopen(lib_path[0]) 16 | -------------------------------------------------------------------------------- /.travis/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | PYENV_ROOT="$HOME/.pyenv" 7 | PATH="$PYENV_ROOT/bin:$PATH" 8 | eval "$(pyenv init -)" 9 | source ~/.venv/bin/activate 10 | 11 | export LDFLAGS="-L$(brew --prefix openssl)/lib" 12 | export CFLAGS="-I$(brew --prefix openssl)/include" 13 | 14 | tox 15 | 16 | MACOSX_DEPLOYMENT_TARGET="10.7" 17 | PLATFORM_NAME="macosx_10_7_intel" 18 | python setup.py bdist_wheel --plat-name "$PLATFORM_NAME" 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Certitude 2 | ========= 3 | 4 | A small library that makes the OS X keychain-based certificate store available 5 | to OpenSSL in a format that OpenSSL appreciates. 6 | 7 | In the future, this library may expand to become a truly cross-platform tool 8 | that pastes over the differences between platforms. 9 | 10 | License 11 | ------- 12 | 13 | OS X Cert Store is provided under the MIT License. See the ``LICENSE`` file for 14 | copyright details. 15 | -------------------------------------------------------------------------------- /src/certitude/build.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from cffi import FFI 3 | ffi = FFI() 4 | 5 | ffi.set_source( 6 | "certitude._certitude", None 7 | ) 8 | 9 | ffi.cdef(""" 10 | uint32_t validate_cert_chain(uint8_t **encoded_certs, 11 | size_t *cert_sizes, 12 | size_t cert_count, 13 | const char *hostname); 14 | """) 15 | 16 | if __name__ == '__main__': 17 | ffi.compile() 18 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installing Certitude 2 | ==================== 3 | 4 | Certitude involves bindings to native-code libraries, and is a library that 5 | functions only on Windows and OS X. These two platforms traditionally have 6 | problems with native-code bindings from Python, due to their lack of compilers 7 | or fully-fledged dependency resolution. 8 | 9 | For this reason, while Certitude is available as a source distribution, it also 10 | provides pre-compiled binary wheels for both Windows and OS X. For this reason 11 | it is *strongly preferred* that a recent pip is used for installing Certitude, 12 | as this will remove the need for a compiler toolchain that you may not have 13 | installed. 14 | 15 | To install Certitude, the pip command is very simple: 16 | 17 | .. code-block:: bash 18 | 19 | $ pip install certitude 20 | 21 | This should download and install a wheel that makes certitude available. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Cory Benfield, Glyph Lefkowitz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. certitude documentation master file, created by 2 | sphinx-quickstart on Tue Mar 15 16:45:56 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Certitude: Platform-Specific TLS Certificate Verification 7 | ========================================================= 8 | 9 | Certitude provides Python bindings to the Windows and OS X system-native TLS 10 | certificate libraries. This allows Python programs to validate TLS certificates 11 | using the exact same logic used by native browsers on those platforms (e.g. 12 | Safari and Internet Explorer/Edge). 13 | 14 | This means that by combining Certitude with the OpenSSL used by default on most 15 | Linuxes and BSDs, it is possible for a Python program to perform certificate 16 | validation in a platform-native manner on all major operating systems. This 17 | lets a Python program behave like a full native citizen of whatever platform it 18 | is installed on. 19 | 20 | This documentation discusses how to use Certitude. 21 | 22 | Contents: 23 | 24 | .. toctree:: 25 | :maxdepth: 2 26 | 27 | installation 28 | using-certitude 29 | -------------------------------------------------------------------------------- /src/certitude/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Defines the public API to certitude. This includes wrapping the C-level 4 | function with some convenience wrappers. 5 | """ 6 | from .certitude import ffi, lib 7 | 8 | 9 | def validate_cert_chain(certs, hostname): 10 | """ 11 | Validate a specific cert chain is valid for a TLS connection to hostname. 12 | 13 | :param certs: A list of DER-encoded certificates. 14 | :param hostname: A unicode string containing the contacted hostname. 15 | :returns: True if the certificates are valid, False otherwise. 16 | """ 17 | # TODO: Raise error codes with appropriate messages instead. 18 | encoded_certs, lengths = zip(*[ 19 | (ffi.new("uint8_t[]", cert), len(cert)) for cert in certs 20 | ]) 21 | cert_ptr_buffer = ffi.new("uint8_t*[]", encoded_certs) 22 | cert_size_buffer = ffi.new("size_t[]", lengths) 23 | cert_count = ffi.new("int *", len(certs)) 24 | hostname = ffi.new("char[]", hostname.encode('utf-8')) 25 | 26 | result = lib.validate_cert_chain( 27 | cert_ptr_buffer, 28 | cert_size_buffer, 29 | cert_count[0], 30 | hostname, 31 | ) 32 | return result == 1 33 | -------------------------------------------------------------------------------- /.travis/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | # Update Homebrew and install Rust 7 | brew update 8 | brew install rust 9 | brew outdated openssl || brew upgrade openssl 10 | 11 | # Install PyEnv 12 | git clone https://github.com/yyuu/pyenv.git ~/.pyenv 13 | PYENV_ROOT="$HOME/.pyenv" 14 | PATH="$PYENV_ROOT/bin:$PATH" 15 | eval "$(pyenv init -)" 16 | 17 | case "${TOXENV}" in 18 | py27) 19 | curl -O https://bootstrap.pypa.io/get-pip.py 20 | python get-pip.py --user 21 | ;; 22 | py33) 23 | pyenv install 3.3.6 24 | pyenv global 3.3.6 25 | ;; 26 | py34) 27 | pyenv install 3.4.6 28 | pyenv global 3.4.6 29 | ;; 30 | py35) 31 | pyenv install 3.5.3 32 | pyenv global 3.5.3 33 | ;; 34 | py36) 35 | pyenv install 3.6.2 36 | pyenv global 3.6.2 37 | ;; 38 | pypy*) 39 | pyenv install pypy-5.7.1 40 | pyenv global pypy-5.7.1 41 | ;; 42 | esac 43 | pyenv rehash 44 | python -m pip install --user virtualenv 45 | python -m virtualenv ~/.venv 46 | source ~/.venv/bin/activate 47 | 48 | export LDFLAGS="-L$(brew --prefix openssl)/lib" 49 | export CFLAGS="-I$(brew --prefix openssl)/include" 50 | pip install -U tox wheel 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - language: generic 6 | os: osx 7 | env: TOXENV=py27 8 | - language: generic 9 | os: osx 10 | env: TOXENV=py34 11 | - language: generic 12 | os: osx 13 | env: TOXENV=py35 14 | - language: generic 15 | os: osx 16 | env: TOXENV=py36 17 | - language: generic 18 | os: osx 19 | env: TOXENV=pypy 20 | 21 | install: 22 | - "./.travis/install.sh" 23 | 24 | script: 25 | - "./.travis/run.sh" 26 | 27 | deploy: 28 | provider: releases 29 | api_key: 30 | secure: U8hnrZSRHNhF7ng+eqkKvcUR+hr+pPeq+Cem3dIJskd0g7+JHlB4c7kS0w85JC0Y7zLJf4/2Og3y0WI5Bd3uMPhArp9woP29OLrd+8HrGMMyQsWWe2ZfqL/EWyblKCIE+z6lqE+DiUI6RWIxXqiOj8hW8nkuZCEN3voXBjrBOPsvx12rAXcCTmbxA6i2xAyvOJN6HxzPsIPYdzwKJnKoji3A2EodbUK6W8/Hrpo7kDq8NUxL6wo4X98dt+Pc++6cWR5eXeoGDCsiyGVoptuxWSzixjKMKNz8n9CzgdcrTi0TPkVhBkwK65K9uulrx/yAXpPkFSJiT+/ll4N4CQrs1383QGUSVyy3/lek4jhhgwN6ByIB/5XoICZIaTw3etz7B7iaYyxQsNA3wlgIj6U8wJhP1nrIRFeIqI4/PPQaH6K+1UJGd9NZbMDgEe2flhjuqZWh2/ODSlirMrHmnE8I5SNgv5hKezEJhwwGa8quSX643Dxbp631IfgtP3fvQ807xqQE65K8mj+aAl3chIgJcX/cxnbmN4ZPPds8U4QTSR2MZZNizaFHxnwdHk32puMWM86cq/i+3riGtTSXlIaY9mm0ylqwMAt/KJwAJqTz91vBPkRM+ebSFq+xf8mLJlRzDd9vfL2Cjd9kBwokufezLsDg2Ewo39TgTMsAAcmSNr4= 31 | file: dist/*.whl 32 | file_glob: true 33 | skip_cleanup: true 34 | on: 35 | tags: true 36 | -------------------------------------------------------------------------------- /test/fixtures/selfsigned-badssl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDhjCCAm6gAwIBAgIJAJz01Wfo02AcMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV 3 | BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp 4 | c2NvMQ8wDQYDVQQKDAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTAeFw0x 5 | NTA4MjgwMTIwNDZaFw0xNzA4MjcwMTIwNDZaMGIxCzAJBgNVBAYTAlVTMRMwEQYD 6 | VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQK 7 | DAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEB 8 | BQADggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2 9 | PmzAS2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMW 10 | hyefdOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3A 11 | xPxTuW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqve 12 | ww9HdFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SY 13 | QCeFxxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaM/MD0wCQYDVR0T 14 | BAIwADALBgNVHQ8EBAMCBeAwIwYDVR0RBBwwGoIMKi5iYWRzc2wuY29tggpiYWRz 15 | c2wuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAVIAuObGCbdR7kuSlbzSSFKEPiJVxK 16 | qQfLMmJ8MfcwOYj4sQ6J8w0jRjyoThw5Uuij7/ewCDCMydd2Ao5iNrrBNyYgc3xN 17 | Bg4cpHSQ2YeCA8RH6dLlSS6BK0aEvXf2268DFgGYtAxf+CJOprqodxd8mBEa5nYL 18 | wlA60SiT0kRY3ASSURtgMjNMKw492Gd5eF/YG94Y4t4UqNMok9YKtJs4HLx/uqXM 19 | u0H/lCumIUKqCcSwpaj1iwYpWeNT3llq+weQUGPV3rMLXZqaCXynz/cxzlmg1cEs 20 | UK4zrvqIB6ah1dFxiuXEBYMejxo6eFliMHG7nTv10jpiv4wgqi4q5VGk 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test/test_basic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test basic Certitude functionality. 4 | """ 5 | import certitude 6 | 7 | 8 | class TestValidation(object): 9 | def test_basic_validation(self, certifi_chain): 10 | """ 11 | We can safely validate a good certificate chain. 12 | 13 | Note that this certificate chain belongs to certifi.io, and will expire 14 | in 2018. 15 | """ 16 | assert certitude.validate_cert_chain( 17 | certifi_chain, u'certifi.io' 18 | ) 19 | 20 | def test_hostname_validation(self, certifi_chain): 21 | """ 22 | We fail to validate if the hostname doesn't match the provided cert. 23 | """ 24 | assert not certitude.validate_cert_chain( 25 | certifi_chain, u'http2bin.org' 26 | ) 27 | 28 | def test_reject_expired(self, expired): 29 | """ 30 | We fail to validate expired certificates. 31 | """ 32 | assert not certitude.validate_cert_chain( 33 | expired, u'expired.badssl.com' 34 | ) 35 | 36 | def test_reject_invalid_host(self, wrong_host): 37 | """ 38 | We fail to validate certificates that don't match their host. 39 | """ 40 | assert not certitude.validate_cert_chain( 41 | wrong_host, u'wrong.host.badssl.com' 42 | ) 43 | 44 | def test_reject_self_signed(self, self_signed): 45 | """ 46 | We fail to validate self-signed certs. 47 | """ 48 | assert not certitude.validate_cert_chain( 49 | self_signed, u'self-signed.badssl.com' 50 | ) 51 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import requests 4 | 5 | from invoke import task 6 | 7 | 8 | API_URL = 'https://ci.appveyor.com/api' 9 | TOKEN = os.environ["APPVEYOR_API_TOKEN"] 10 | ACCOUNT_NAME = "lukasa" 11 | PROJECT_NAME = "certitude" 12 | BASE_URL = "https://ci.appveyor.com/api" 13 | 14 | 15 | @task 16 | def download_artifacts(build_id): 17 | s = requests.Session() 18 | s.headers = { 19 | "Authorization": "Bearer {}".format(TOKEN) 20 | } 21 | 22 | # Get project build details. 23 | print "Obtaining artifacts for {}".format(PROJECT_NAME) 24 | build_details_url = BASE_URL + "/projects/{}/{}/build/{}".format( 25 | ACCOUNT_NAME, PROJECT_NAME, build_id 26 | ) 27 | r = s.get(build_details_url, stream=True) 28 | r.raise_for_status() 29 | 30 | for job in r.json()[u'build'][u'jobs']: 31 | # Get the artifact list because we don't know what the filename is. 32 | job_id = job[u'jobId'] 33 | artifacts_url = BASE_URL + "/buildjobs/{}/artifacts/".format(job_id) 34 | 35 | print "Artifacts for {}".format(job_id) 36 | r = s.get(artifacts_url, stream=True) 37 | r.raise_for_status() 38 | 39 | artifact_name = r.json()[0][u'fileName'] 40 | artifact = BASE_URL + "/buildjobs/{}/artifacts/{}".format( 41 | job_id, artifact_name 42 | ) 43 | 44 | print "Downloading {} to {}".format(artifact, artifact_name) 45 | r = s.get(artifact, stream=True) 46 | r.raise_for_status() 47 | 48 | with open("{}".format(artifact_name), 'wb') as f: 49 | for chunk in r.iter_content(4096): 50 | f.write(chunk) 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os.path 3 | 4 | from setuptools import setup, find_packages 5 | 6 | from rust_ext import build_rust_cmdclass, install_lib_including_rust 7 | 8 | long_description = ( 9 | open("README.rst").read() 10 | ) 11 | 12 | setup( 13 | name="certitude", 14 | version="1.0.1", 15 | 16 | description="A library that provides access to system certificate stores.", 17 | long_description=open("README.rst").read(), 18 | url="https://github.com/python-hyper/certitude/", 19 | license="MIT", 20 | 21 | author="Cory Benfield", 22 | author_email="cory@lukasa.co.uk", 23 | 24 | setup_requires=[ 25 | "cffi>=1.0.0", 26 | ], 27 | install_requires=[ 28 | "cffi>=1.0.0", 29 | ], 30 | 31 | cffi_modules=["src/certitude/build.py:ffi"], 32 | 33 | packages=find_packages('src'), 34 | package_dir={'': 'src'}, 35 | 36 | cmdclass={ 37 | "build_rust": build_rust_cmdclass( 38 | os.path.join("src", "rust-certitude", "c-certitude", "Cargo.toml") 39 | ), 40 | "install_lib": install_lib_including_rust, 41 | }, 42 | ext_package="certitude", 43 | 44 | classifiers=[ 45 | "Programming Language :: Python :: Implementation :: CPython", 46 | "Programming Language :: Python :: Implementation :: PyPy", 47 | "Programming Language :: Python :: 2", 48 | "Programming Language :: Python :: 2.7", 49 | "Programming Language :: Python :: 3", 50 | "Programming Language :: Python :: 3.4", 51 | "Programming Language :: Python :: 3.5", 52 | "Programming Language :: Python :: 3.6", 53 | ] 54 | ) 55 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | conftest.py 4 | ~~~~~~~~~~~ 5 | 6 | Py.test fixtures. 7 | """ 8 | import os.path 9 | 10 | import pytest 11 | 12 | from cryptography import x509 13 | from cryptography.hazmat.backends import default_backend 14 | from cryptography.hazmat.primitives.serialization import Encoding 15 | 16 | 17 | CERT_DELIMITER = '-----END CERTIFICATE-----\n' 18 | HERE = os.path.dirname(__file__) 19 | 20 | 21 | def build_chain_from_pem(chain): 22 | """ 23 | Splits a PEM cert chain and converts them to DER bytes. 24 | """ 25 | certs = chain.split(CERT_DELIMITER) 26 | 27 | parsed_certs = ( 28 | x509.load_pem_x509_certificate( 29 | (cert+CERT_DELIMITER).encode('ascii'), 30 | default_backend() 31 | ) 32 | for cert in certs if cert 33 | ) 34 | encoded_certs = [ 35 | cert.public_bytes(Encoding.DER) for cert in parsed_certs 36 | ] 37 | return encoded_certs 38 | 39 | 40 | @pytest.fixture 41 | def certifi_chain(): 42 | """ 43 | Returns the trust chain associated with certifi.io. Note that this expires 44 | in 2018. 45 | """ 46 | with open(os.path.join(HERE, 'fixtures', 'certifi_chain.pem'), 'r') as f: 47 | chain = f.read() 48 | 49 | encoded_certs = build_chain_from_pem(chain) 50 | assert len(encoded_certs) == 3 51 | return encoded_certs 52 | 53 | 54 | @pytest.fixture 55 | def expired(): 56 | """ 57 | Returns the cert chain from expired.badssl.com. 58 | """ 59 | with open(os.path.join(HERE, 'fixtures', 'expired-badssl.pem'), 'r') as f: 60 | chain = f.read() 61 | 62 | encoded_certs = build_chain_from_pem(chain) 63 | assert len(encoded_certs) == 3 64 | return encoded_certs 65 | 66 | 67 | @pytest.fixture 68 | def wrong_host(): 69 | """ 70 | Returns the cert chain from wrong.host.badssl.com. 71 | """ 72 | path = os.path.join(HERE, 'fixtures', 'wronghost-badssl.pem') 73 | with open(path, 'r') as f: 74 | chain = f.read() 75 | 76 | encoded_certs = build_chain_from_pem(chain) 77 | assert len(encoded_certs) == 3 78 | return encoded_certs 79 | 80 | 81 | @pytest.fixture 82 | def self_signed(): 83 | """ 84 | Returns the cert chain from selfsigned.badssl.com. 85 | """ 86 | path = os.path.join(HERE, 'fixtures', 'selfsigned-badssl.pem') 87 | with open(path, 'r') as f: 88 | chain = f.read() 89 | 90 | encoded_certs = build_chain_from_pem(chain) 91 | assert len(encoded_certs) == 1 92 | return encoded_certs 93 | -------------------------------------------------------------------------------- /docs/source/using-certitude.rst: -------------------------------------------------------------------------------- 1 | Using Certitude 2 | =============== 3 | 4 | Certitude has one job: validating the TLS certificates a server has sent you. 5 | To do that, you need to pass Certitude the TLS certificate chain sent by the 6 | server, and the hostname you're expecting to connect to. 7 | 8 | Getting A Certificate Chain 9 | --------------------------- 10 | 11 | Certitude expects the TLS certificate chain as a list of TLS certificates 12 | stored in the DER representation. Unfortunately, the Python standard library's 13 | ``ssl`` module is not capable of providing the entire certificate chain, only 14 | the leaf certificate. This means that to use Certitude you will need to use 15 | `pyopenssl`_ or something like it: it's just the only way to guarantee that 16 | you get the complete certificate chain. 17 | 18 | To get a certificate chain from PyOpenSSL, you'll want to make the connection 19 | as normal and then call ``get_peer_cert_chain()``. This will get you your cert 20 | chain as a list of ``X509`` objects. These will need decoding. 21 | 22 | Given an already existing connection ``cnx``, you can get your list of 23 | certificates like this: 24 | 25 | .. code-block:: python 26 | 27 | certs = cnx.get_peer_cert_chain() 28 | 29 | encoded_certs = [ 30 | OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert) 31 | for cert in certs 32 | ] 33 | 34 | Validating The Chain 35 | -------------------- 36 | 37 | Once you have the chain in place, it's simple enough to validate it. Simply 38 | pass the chain into ``certitude.validate_cert_chain`` along with a unicode 39 | string containing the expected hostname. For example: 40 | 41 | .. code-block:: python 42 | 43 | valid = validate_cert_chain(encoded_certs, u'http2bin.org') 44 | 45 | 46 | The ``validate_cert_chain`` function returns ``True`` if the cert chain is 47 | valid, and ``False`` in any other case. 48 | 49 | Notes 50 | ----- 51 | 52 | When validating certificates using certitude you'll likely want to *disable* 53 | OpenSSL's certificate validation. This is because OpenSSL and the 54 | platform-specific TLS validation code will build their certificate chains 55 | differently. In particular, OpenSSL may be *unable* to validate a chain that 56 | the system library believes is valid. For that reason, put OpenSSL into the 57 | ``VERIFY_NONE`` mode and then handle the validation manually, *after* the 58 | connection is made but **before you send any data on it**. 59 | 60 | We cannot stress this enough: **you must validate the certificates before 61 | sending or receiving data on the connection**. 62 | 63 | 64 | .. _pyopenssl: https://pyopenssl.readthedocs.io/en/stable/ 65 | -------------------------------------------------------------------------------- /appveyor/build.bat: -------------------------------------------------------------------------------- 1 | REM Copyright 2015 Brian Smith. 2 | REM Copyright 2016 Cory Benfield. 3 | REM 4 | REM Permission to use, copy, modify, and/or distribute this software for any 5 | REM purpose with or without fee is hereby granted, provided that the above 6 | REM copyright notice and this permission notice appear in all copies. 7 | REM 8 | REM THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 9 | REM WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | REM MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 11 | REM ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | REM WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | REM ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | 15 | echo on 16 | SetLocal EnableDelayedExpansion 17 | 18 | REM This is the recommended way to choose the toolchain version, according to 19 | REM Appveyor's documentation. 20 | SET PATH=C:\Program Files (x86)\MSBuild\%TOOLCHAIN_VERSION%\Bin;%PATH% 21 | 22 | set VCVARSALL="C:\Program Files (x86)\Microsoft Visual Studio %TOOLCHAIN_VERSION%\VC\vcvarsall.bat" 23 | 24 | if [%Platform%] NEQ [x64] goto win32 25 | set TARGET_ARCH=x86_64 26 | set TARGET_PROGRAM_FILES=%ProgramFiles% 27 | set WHEEL_PLAT_NAME=win_amd64 28 | call %VCVARSALL% amd64 29 | if %ERRORLEVEL% NEQ 0 exit 1 30 | goto download 31 | 32 | :win32 33 | echo on 34 | if [%Platform%] NEQ [Win32] exit 1 35 | set TARGET_ARCH=i686 36 | set TARGET_PROGRAM_FILES=%ProgramFiles(x86)% 37 | set WHEEL_PLAT_NAME=win32 38 | call %VCVARSALL% amd64_x86 39 | if %ERRORLEVEL% NEQ 0 exit 1 40 | goto download 41 | 42 | :download 43 | REM vcvarsall turns echo off 44 | echo on 45 | 46 | REM Install Rust 47 | set RUST_URL=https://static.rust-lang.org/dist/rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi 48 | echo Downloading %RUST_URL%... 49 | mkdir build 50 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%RUST_URL%', 'build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi')" 51 | if %ERRORLEVEL% NEQ 0 ( 52 | echo ...downloading failed. 53 | exit 1 54 | ) 55 | 56 | start /wait msiexec /i build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi INSTALLDIR="%TARGET_PROGRAM_FILES%\Rust %RUST%" /quiet /qn /norestart 57 | if %ERRORLEVEL% NEQ 0 exit 1 58 | 59 | set PATH="%PYTHON%";"%PYTHON%\Scripts";"%TARGET_PROGRAM_FILES%\Rust %RUST%\bin";%PATH% 60 | 61 | link /? 62 | cl /? 63 | rustc --version 64 | cargo --version 65 | 66 | python -m pip install -U setuptools pip wheel 67 | if %ERRORLEVEL% NEQ 0 exit 1 68 | 69 | python -m pip install -r test_requirements.txt 70 | if %ERRORLEVEL% NEQ 0 exit 1 71 | 72 | python -m pip install . 73 | if %ERRORLEVEL% NEQ 0 exit 1 74 | 75 | py.test test/ 76 | if %ERRORLEVEL% NEQ 0 exit 1 77 | 78 | REM We only need one 64-bit wheel and one 32-bit wheel for Windows. 79 | python setup.py bdist_wheel --plat-name %WHEEL_PLAT_NAME% 80 | if %ERRORLEVEL% NEQ 0 exit 1 81 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | os: 3 | - Visual Studio 2015 4 | clone_depth: 1 5 | environment: 6 | matrix: 7 | - TOOLCHAIN_VERSION: 14.0 8 | RUST: 1.17.0 9 | PYTHON: "C:\\Python36" 10 | platform: Win32 11 | 12 | - TOOLCHAIN_VERSION: 14.0 13 | RUST: beta 14 | PYTHON: "C:\\Python36" 15 | platform: Win32 16 | 17 | - TOOLCHAIN_VERSION: 14.0 18 | RUST: nightly 19 | PYTHON: "C:\\Python36" 20 | platform: Win32 21 | 22 | - TOOLCHAIN_VERSION: 14.0 23 | RUST: 1.17.0 24 | PYTHON: "C:\\Python36-x64" 25 | platform: x64 26 | 27 | - TOOLCHAIN_VERSION: 14.0 28 | RUST: beta 29 | PYTHON: "C:\\Python36-x64" 30 | platform: x64 31 | 32 | - TOOLCHAIN_VERSION: 14.0 33 | RUST: nightly 34 | PYTHON: "C:\\Python36-x64" 35 | platform: x64 36 | 37 | - TOOLCHAIN_VERSION: 14.0 38 | RUST: 1.17.0 39 | PYTHON: "C:\\Python35" 40 | platform: Win32 41 | 42 | - TOOLCHAIN_VERSION: 14.0 43 | RUST: beta 44 | PYTHON: "C:\\Python35" 45 | platform: Win32 46 | 47 | - TOOLCHAIN_VERSION: 14.0 48 | RUST: nightly 49 | PYTHON: "C:\\Python35" 50 | platform: Win32 51 | 52 | - TOOLCHAIN_VERSION: 14.0 53 | RUST: 1.17.0 54 | PYTHON: "C:\\Python35-x64" 55 | platform: x64 56 | 57 | - TOOLCHAIN_VERSION: 14.0 58 | RUST: beta 59 | PYTHON: "C:\\Python35-x64" 60 | platform: x64 61 | 62 | - TOOLCHAIN_VERSION: 14.0 63 | RUST: nightly 64 | PYTHON: "C:\\Python35-x64" 65 | platform: x64 66 | 67 | - TOOLCHAIN_VERSION: 14.0 68 | RUST: 1.17.0 69 | PYTHON: "C:\\Python34" 70 | platform: Win32 71 | 72 | - TOOLCHAIN_VERSION: 14.0 73 | RUST: beta 74 | PYTHON: "C:\\Python34" 75 | platform: Win32 76 | 77 | - TOOLCHAIN_VERSION: 14.0 78 | RUST: nightly 79 | PYTHON: "C:\\Python34" 80 | platform: Win32 81 | 82 | - TOOLCHAIN_VERSION: 14.0 83 | RUST: 1.17.0 84 | PYTHON: "C:\\Python34-x64" 85 | DISTUTILS_USE_SDK: "1" 86 | platform: x64 87 | 88 | - TOOLCHAIN_VERSION: 14.0 89 | RUST: beta 90 | PYTHON: "C:\\Python34-x64" 91 | DISTUTILS_USE_SDK: "1" 92 | platform: x64 93 | 94 | - TOOLCHAIN_VERSION: 14.0 95 | RUST: nightly 96 | PYTHON: "C:\\Python34-x64" 97 | DISTUTILS_USE_SDK: "1" 98 | platform: x64 99 | 100 | - TOOLCHAIN_VERSION: 14.0 101 | RUST: 1.17.0 102 | PYTHON: "C:\\Python27" 103 | platform: Win32 104 | 105 | - TOOLCHAIN_VERSION: 14.0 106 | RUST: beta 107 | PYTHON: "C:\\Python27" 108 | platform: Win32 109 | 110 | - TOOLCHAIN_VERSION: 14.0 111 | RUST: nightly 112 | PYTHON: "C:\\Python27" 113 | platform: Win32 114 | 115 | - TOOLCHAIN_VERSION: 14.0 116 | RUST: 1.17.0 117 | PYTHON: "C:\\Python27-x64" 118 | platform: x64 119 | 120 | - TOOLCHAIN_VERSION: 14.0 121 | RUST: beta 122 | PYTHON: "C:\\Python27-x64" 123 | platform: x64 124 | 125 | - TOOLCHAIN_VERSION: 14.0 126 | RUST: nightly 127 | PYTHON: "C:\\Python27-x64" 128 | platform: x64 129 | 130 | matrix: 131 | allow_failures: 132 | - RUST: nightly 133 | 134 | install: 135 | - "git submodule update --init --recursive" 136 | 137 | build_script: appveyor/build.bat 138 | 139 | artifacts: 140 | - path: dist\* -------------------------------------------------------------------------------- /test/fixtures/certifi_chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE7TCCA9WgAwIBAgIQPJv9Nr5sUeqvIbh/ytLf0DANBgkqhkiG9w0BAQsFADBf 3 | MQswCQYDVQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlzMQ4w 4 | DAYDVQQKEwVHYW5kaTEgMB4GA1UEAxMXR2FuZGkgU3RhbmRhcmQgU1NMIENBIDIw 5 | HhcNMTUwOTE2MDAwMDAwWhcNMTgwOTE2MjM1OTU5WjBVMSEwHwYDVQQLExhEb21h 6 | aW4gQ29udHJvbCBWYWxpZGF0ZWQxGzAZBgNVBAsTEkdhbmRpIFN0YW5kYXJkIFNT 7 | TDETMBEGA1UEAxMKY2VydGlmaS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 8 | AQoCggEBAMPv/RyJiCmIN2wbM1X9sGZI3aRAkFYuSsZB0y1CC+wA1hZ7BLOusx/A 9 | Makv7IL/XQKWHV4HS5t6Ak0IpxwkdOCkKl6MESfGNgyZuGB9edHtMpR+ntO4RK93 10 | HqzL5TpnfQDmI836HW+KpZ4UIGq01ZWbu06BVfGg+fTI9MxD69iTbagAXAbbr8EL 11 | QJ/2TEiG7jzPc7zQfnNbF9Cbhcf6FzG5TN9Jxzru8z8pBa5RBOmz52K82rpAMXku 12 | yZkynJHxlG8xumvbPtGZxbeHIE7kzkHSxnl56RkFXL+g+b9lWCCjyHZeolyohd0W 13 | 2/j1QGYrU6d/8GGHFbXLTlv9j21VyckCAwEAAaOCAa0wggGpMB8GA1UdIwQYMBaA 14 | FLOQp9jJr07NYTyffK1df0H9aTDqMB0GA1UdDgQWBBRm4PqNZfYZWsealJVUx/Hf 15 | cd03STAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggr 16 | BgEFBQcDAQYIKwYBBQUHAwIwSwYDVR0gBEQwQjA2BgsrBgEEAbIxAQICGjAnMCUG 17 | CCsGAQUFBwIBFhlodHRwczovL2Nwcy51c2VydHJ1c3QuY29tMAgGBmeBDAECATBB 18 | BgNVHR8EOjA4MDagNKAyhjBodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vR2FuZGlT 19 | dGFuZGFyZFNTTENBMi5jcmwwcwYIKwYBBQUHAQEEZzBlMDwGCCsGAQUFBzAChjBo 20 | dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vR2FuZGlTdGFuZGFyZFNTTENBMi5jcnQw 21 | JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wJQYDVR0RBB4w 22 | HIIKY2VydGlmaS5pb4IOd3d3LmNlcnRpZmkuaW8wDQYJKoZIhvcNAQELBQADggEB 23 | AHKIJnecQMHXzBvFYZBAByKvbkK4jvzmPO/H+y23N7R2Du+gScHnaS+tAdrEp4ZN 24 | +gDNqsHiYGf6bYlLHXtVrRSHUD+Og83uJxif/LH4MCwhxkU4K0KjsoPLUdGHBuAF 25 | PqrDByO/UEAtUPRps3I2FAF8zUFsnBPUI2/wOayLAkGILdPYi+yZD3ntTqytvZre 26 | fhc0Kj2QSkYFAP0aKtHy4dvWlFLMKAD8Y8t19HGJXJHqwGyZHZR2eRy439HwlVps 27 | 7VwmDseyqZ6nyG5fOuD4kUWZLqh+pv6kGA7S86DNU+RxBVd7I7IBFGZMLbFxPv7B 28 | +Xd6AGde3tCqJ2lXhqIP4k8= 29 | -----END CERTIFICATE----- 30 | -----BEGIN CERTIFICATE----- 31 | MIIF6TCCA9GgAwIBAgIQBeTcO5Q4qzuFl8umoZhQ4zANBgkqhkiG9w0BAQwFADCB 32 | iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl 33 | cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV 34 | BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQw 35 | OTEyMDAwMDAwWhcNMjQwOTExMjM1OTU5WjBfMQswCQYDVQQGEwJGUjEOMAwGA1UE 36 | CBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlzMQ4wDAYDVQQKEwVHYW5kaTEgMB4GA1UE 37 | AxMXR2FuZGkgU3RhbmRhcmQgU1NMIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB 38 | DwAwggEKAoIBAQCUBC2meZV0/9UAPPWu2JSxKXzAjwsLibmCg5duNyj1ohrP0pIL 39 | m6jTh5RzhBCf3DXLwi2SrCG5yzv8QMHBgyHwv/j2nPqcghDA0I5O5Q1MsJFckLSk 40 | QFEW2uSEEi0FXKEfFxkkUap66uEHG4aNAXLy59SDIzme4OFMH2sio7QQZrDtgpbX 41 | bmq08j+1QvzdirWrui0dOnWbMdw+naxb00ENbLAb9Tr1eeohovj0M1JLJC0epJmx 42 | bUi8uBL+cnB89/sCdfSN3tbawKAyGlLfOGsuRTg/PwSWAP2h9KK71RfWJ3wbWFmV 43 | XooS/ZyrgT5SKEhRhWvzkbKGPym1bgNi7tYFAgMBAAGjggF1MIIBcTAfBgNVHSME 44 | GDAWgBRTeb9aqitKz1SA4dibwJ3ysgNmyzAdBgNVHQ4EFgQUs5Cn2MmvTs1hPJ98 45 | rV1/Qf1pMOowDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD 46 | VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMCIGA1UdIAQbMBkwDQYLKwYBBAGy 47 | MQECAhowCAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNl 48 | cnRydXN0LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNy 49 | bDB2BggrBgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRy 50 | dXN0LmNvbS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZ 51 | aHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAWGf9 52 | crJq13xhlhl+2UNG0SZ9yFP6ZrBrLafTqlb3OojQO3LJUP33WbKqaPWMcwO7lWUX 53 | zi8c3ZgTopHJ7qFAbjyY1lzzsiI8Le4bpOHeICQW8owRc5E69vrOJAKHypPstLbI 54 | FhfFcvwnQPYT/pOmnVHvPCvYd1ebjGU6NSU2t7WKY28HJ5OxYI2A25bUeo8tqxyI 55 | yW5+1mUfr13KFj8oRtygNeX56eXVlogMT8a3d2dIhCe2H7Bo26y/d7CQuKLJHDJd 56 | ArolQ4FCR7vY4Y8MDEZf7kYzawMUgtN+zY+vkNaOJH1AQrRqahfGlZfh8jjNp+20 57 | J0CT33KpuMZmYzc4ZCIwojvxuch7yPspOqsactIGEk72gtQjbz7Dk+XYtsDe3CMW 58 | 1hMwt6CaDixVBgBwAc/qOR2A24j3pSC4W/0xJmmPLQphgzpHphNULB7j7UTKvGof 59 | KA5R2d4On3XNDgOVyvnFqSot/kGkoUeuDcL5OWYzSlvhhChZbH2UF3bkRYKtcCD9 60 | 0m9jqNf6oDP6N8v3smWe2lBvP+Sn845dWDKXcCMu5/3EFZucJ48y7RetWIExKREa 61 | m9T8bJUox04FB6b9HbwZ4ui3uRGKLXASUoWNjDNKD/yZkuBjcNqllEdjB+dYxzFf 62 | BT02Vf6Dsuimrdfp5gJ0iHRc2jTbkNJtUQoj1iM= 63 | -----END CERTIFICATE----- 64 | -----BEGIN CERTIFICATE----- 65 | MIIFdzCCBF+gAwIBAgIQE+oocFv07O0MNmMJgGFDNjANBgkqhkiG9w0BAQwFADBv 66 | MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk 67 | ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF 68 | eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow 69 | gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtK 70 | ZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYD 71 | VQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjAN 72 | BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sIs9CsVw127c0n00yt 73 | UINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnGvDoZtF+mvX2do2NC 74 | tnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQIjy8/hPwhxR79uQf 75 | jtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfbIWax1Jt4A8BQOujM 76 | 8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0tyA9yn8iNK5+O2hm 77 | AUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97Exwzf4TKuzJM7UXiV 78 | Z4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNVicQNwZNUMBkTrNN9 79 | N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5D9kCnusSTJV882sF 80 | qV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJWBp/kjbmUZIO8yZ9 81 | HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ5lhCLkMaTLTwJUdZ 82 | +gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzGKAgEJTm4Diup8kyX 83 | HAc/DVL17e8vgg8CAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTv 84 | A73gJMtUGjAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/ 85 | BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1Ud 86 | HwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4 87 | dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0 88 | dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAJNl9jeD 89 | lQ9ew4IcH9Z35zyKwKoJ8OkLJvHgwmp1ocd5yblSYMgpEg7wrQPWCcR23+WmgZWn 90 | RtqCV6mVksW2jwMibDN3wXsyF24HzloUQToFJBv2FAY7qCUkDrvMKnXduXBBP3zQ 91 | YzYhBx9G/2CkkeFnvN4ffhkUyWNnkepnB2u0j4vAbkN9w6GAbLIevFOFfdyQoaS8 92 | Le9Gclc1Bb+7RrtubTeZtv8jkpHGbkD4jylW6l/VXxRTrPBPYer3IsynVgviuDQf 93 | Jtl7GQVoP7o81DgGotPmjw7jtHFtQELFhLRAlSv0ZaBIefYdgWOWnU914Ph85I6p 94 | 0fKtirOMxyHNwu8= 95 | -----END CERTIFICATE----- 96 | -------------------------------------------------------------------------------- /test/fixtures/expired-badssl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFSzCCBDOgAwIBAgIQSueVSfqavj8QDxekeOFpCTANBgkqhkiG9w0BAQsFADCB 3 | kDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G 4 | A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNV 5 | BAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD 6 | QTAeFw0xNTA0MDkwMDAwMDBaFw0xNTA0MTIyMzU5NTlaMFkxITAfBgNVBAsTGERv 7 | bWFpbiBDb250cm9sIFZhbGlkYXRlZDEdMBsGA1UECxMUUG9zaXRpdmVTU0wgV2ls 8 | ZGNhcmQxFTATBgNVBAMUDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 9 | ggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2PmzA 10 | S2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMWhyef 11 | dOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3AxPxT 12 | uW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqveww9H 13 | dFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SYQCeF 14 | xxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaOCAdUwggHRMB8GA1Ud 15 | IwQYMBaAFJCvajqUWgvYkOoSVnPfQ7Q6KNrnMB0GA1UdDgQWBBSd7sF7gQs6R2lx 16 | GH0RN5O8pRs/+zAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUE 17 | FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQIC 18 | BzArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAI 19 | BgZngQwBAgEwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5jb21vZG9jYS5j 20 | b20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNybDCB 21 | hQYIKwYBBQUHAQEEeTB3ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LmNvbW9kb2Nh 22 | LmNvbS9DT01PRE9SU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0 23 | MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wIwYDVR0RBBww 24 | GoIMKi5iYWRzc2wuY29tggpiYWRzc2wuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBq 25 | evHa/wMHcnjFZqFPRkMOXxQhjHUa6zbgH6QQFezaMyV8O7UKxwE4PSf9WNnM6i1p 26 | OXy+l+8L1gtY54x/v7NMHfO3kICmNnwUW+wHLQI+G1tjWxWrAPofOxkt3+IjEBEH 27 | fnJ/4r+3ABuYLyw/zoWaJ4wQIghBK4o+gk783SHGVnRwpDTysUCeK1iiWQ8dSO/r 28 | ET7BSp68ZVVtxqPv1dSWzfGuJ/ekVxQ8lEEFeouhN0fX9X3c+s5vMaKwjOrMEpsi 29 | 8TRwz311SotoKQwe6Zaoz7ASH1wq7mcvf71z81oBIgxw+s1F73hczg36TuHvzmWf 30 | RwxPuzZEaFZcVlmtqoq8 31 | -----END CERTIFICATE----- 32 | -----BEGIN CERTIFICATE----- 33 | MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB 34 | hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G 35 | A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV 36 | BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy 37 | MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT 38 | EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR 39 | Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh 40 | bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP 41 | ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh 42 | bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0 43 | Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6 44 | ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51 45 | UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n 46 | c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY 47 | MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz 48 | 30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV 49 | HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG 50 | BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv 51 | bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB 52 | AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E 53 | T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v 54 | ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p 55 | mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/ 56 | e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps 57 | P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY 58 | dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc 59 | 2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG 60 | V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4 61 | HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX 62 | j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII 63 | 0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap 64 | lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf 65 | +AZxAeKCINT+b72x 66 | -----END CERTIFICATE----- 67 | -----BEGIN CERTIFICATE----- 68 | MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv 69 | MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk 70 | ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF 71 | eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow 72 | gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO 73 | BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD 74 | VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq 75 | hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw 76 | AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6 77 | 2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr 78 | ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt 79 | 4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq 80 | m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/ 81 | vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT 82 | 8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE 83 | IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO 84 | KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO 85 | GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/ 86 | s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g 87 | JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD 88 | AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9 89 | MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy 90 | bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6 91 | Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ 92 | zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj 93 | Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY 94 | Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5 95 | B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx 96 | PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR 97 | pu/xO28QOG8= 98 | -----END CERTIFICATE----- 99 | -------------------------------------------------------------------------------- /test/fixtures/wronghost-badssl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFSzCCBDOgAwIBAgIQL+sYJRh8GlCGQHtE5beFpTANBgkqhkiG9w0BAQsFADCB 3 | kDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G 4 | A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNV 5 | BAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD 6 | QTAeFw0xNTA0MDkwMDAwMDBaFw0xNjA3MDcyMzU5NTlaMFkxITAfBgNVBAsTGERv 7 | bWFpbiBDb250cm9sIFZhbGlkYXRlZDEdMBsGA1UECxMUUG9zaXRpdmVTU0wgV2ls 8 | ZGNhcmQxFTATBgNVBAMUDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 9 | ggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2PmzA 10 | S2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMWhyef 11 | dOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3AxPxT 12 | uW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqveww9H 13 | dFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SYQCeF 14 | xxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaOCAdUwggHRMB8GA1Ud 15 | IwQYMBaAFJCvajqUWgvYkOoSVnPfQ7Q6KNrnMB0GA1UdDgQWBBSd7sF7gQs6R2lx 16 | GH0RN5O8pRs/+zAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUE 17 | FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQIC 18 | BzArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAI 19 | BgZngQwBAgEwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5jb21vZG9jYS5j 20 | b20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNybDCB 21 | hQYIKwYBBQUHAQEEeTB3ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LmNvbW9kb2Nh 22 | LmNvbS9DT01PRE9SU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0 23 | MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wIwYDVR0RBBww 24 | GoIMKi5iYWRzc2wuY29tggpiYWRzc2wuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCL 25 | OKyT4gZim9wO1SyRrLf+IhQ6kD4TSgCvUIr6uMOaGWvggcjAxQmXT94tqzg93kTC 26 | 3okAjahcXzQReWDFFdcD+etZ5hqDAg4FpG3mR994nDSmO5gaW9BpuSPb7zHRmkoQ 27 | 84x06Sk1M1QPlis/EiLHR8JoIz+psLKK0WkIiCSQVAnkN4R5VpaRuEQh8v5SuLXo 28 | D8o5froZeVFHRAguE8joC8bwmiyGQqWvuNXC6Zq45Ydlo1Vyam+wZuQ/ODlRerrQ 29 | 5TXftG5lE/U32JzTqb3jDy3YTjGpTlujNxY5r5fKGaucTTBsMhnLlj+0Dr4TdE1r 30 | GTh+vUk+sJO+zDDOthzt 31 | -----END CERTIFICATE----- 32 | -----BEGIN CERTIFICATE----- 33 | MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB 34 | hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G 35 | A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV 36 | BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy 37 | MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT 38 | EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR 39 | Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh 40 | bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP 41 | ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh 42 | bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0 43 | Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6 44 | ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51 45 | UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n 46 | c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY 47 | MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz 48 | 30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV 49 | HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG 50 | BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv 51 | bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB 52 | AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E 53 | T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v 54 | ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p 55 | mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/ 56 | e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps 57 | P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY 58 | dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc 59 | 2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG 60 | V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4 61 | HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX 62 | j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII 63 | 0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap 64 | lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf 65 | +AZxAeKCINT+b72x 66 | -----END CERTIFICATE----- 67 | -----BEGIN CERTIFICATE----- 68 | MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv 69 | MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk 70 | ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF 71 | eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow 72 | gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO 73 | BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD 74 | VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq 75 | hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw 76 | AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6 77 | 2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr 78 | ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt 79 | 4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq 80 | m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/ 81 | vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT 82 | 8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE 83 | IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO 84 | KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO 85 | GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/ 86 | s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g 87 | JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD 88 | AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9 89 | MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy 90 | bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6 91 | Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ 92 | zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj 93 | Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY 94 | Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5 95 | B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx 96 | PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR 97 | pu/xO28QOG8= 98 | -----END CERTIFICATE----- 99 | -------------------------------------------------------------------------------- /rust_ext.py: -------------------------------------------------------------------------------- 1 | # From https://github.com/novocaine/rust-python-ext 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2015 James Salter 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | from __future__ import print_function 24 | import platform 25 | import sys 26 | import subprocess 27 | import os.path 28 | import glob 29 | from distutils.cmd import Command 30 | from distutils.command.install_lib import install_lib 31 | import shutil 32 | 33 | 34 | class RustBuildCommand(Command): 35 | """ 36 | Command for building rust crates via cargo. 37 | Don't use this directly; use the build_rust_cmdclass 38 | factory method. 39 | """ 40 | 41 | description = "build rust crates into Python extensions" 42 | 43 | user_options = [] 44 | 45 | def _unpack_classargs(self): 46 | for k, v in self.__class__.args.items(): 47 | setattr(self, k, v) 48 | 49 | def initialize_options(self): 50 | self._unpack_classargs() 51 | 52 | def finalize_options(self): 53 | pass 54 | 55 | def run(self): 56 | if self.debug: 57 | self.debug_or_release = "--debug" 58 | else: 59 | self.debug_or_release = "--release" 60 | 61 | # Make sure that if pythonXX-sys is used, it builds against the current 62 | # executing python interpreter. 63 | bindir = os.path.dirname(sys.executable) 64 | 65 | env = os.environ 66 | # disables rust's pkg-config seeking for specified packages, 67 | # which causes pythonXX-sys to fall back to detecting the 68 | # interpreter from the path. 69 | env["PYTHON_2.7_NO_PKG_CONFIG"] = "1" 70 | env["PATH"] = bindir + os.pathsep + os.environ.get("PATH", "") 71 | 72 | # Execute cargo. 73 | try: 74 | args = (["cargo", "build", "--manifest-path", self.cargo_toml_path, 75 | self.debug_or_release, "--verbose"] + list(self.extra_cargo_args or [])) 76 | if not self.quiet: 77 | print(" ".join(args), file=sys.stderr) 78 | output = subprocess.check_output( 79 | ' '.join(args), 80 | env=env, 81 | shell=True, 82 | stderr=subprocess.STDOUT 83 | ) 84 | except subprocess.CalledProcessError as e: 85 | msg = "cargo failed with code: %d\n%s" % (e.returncode, e.output) 86 | raise Exception(msg) 87 | except OSError: 88 | raise Exception("Unable to execute 'cargo' - this package " 89 | "requires rust to be installed and cargo to be on the PATH") 90 | 91 | if not self.quiet: 92 | print(output, file=sys.stderr) 93 | 94 | # Find the shared library that cargo hopefully produced and copy 95 | # it into the build directory as if it were produced by build_cext. 96 | if self.debug: 97 | suffix = "debug" 98 | else: 99 | suffix = "release" 100 | 101 | target_dir = os.path.join(os.path.dirname(self.cargo_toml_path), 102 | "target/", suffix) 103 | 104 | if sys.platform == "win32": 105 | wildcard_so = "*.dll" 106 | elif sys.platform == "darwin": 107 | wildcard_so = "*.dylib" 108 | else: 109 | wildcard_so = "*.so" 110 | 111 | try: 112 | dylib_path = glob.glob(os.path.join(target_dir, wildcard_so))[0] 113 | except IndexError: 114 | raise Exception("rust build failed; unable to find any .dylib in %s" % 115 | target_dir) 116 | 117 | # Ask build_ext where the shared library would go if it had built it, 118 | # then copy it there. 119 | build_ext = self.get_finalized_command('build_ext') 120 | target_fname = os.path.splitext(os.path.basename(dylib_path)[3:])[0] 121 | ext_path = build_ext.get_ext_fullpath(os.path.basename(target_fname)) 122 | try: 123 | os.makedirs(os.path.dirname(ext_path)) 124 | except OSError: 125 | pass 126 | shutil.copyfile(dylib_path, ext_path) 127 | 128 | 129 | def build_rust_cmdclass(cargo_toml_path, debug=False, 130 | extra_cargo_args=None, quiet=False): 131 | """ 132 | Args: 133 | cargo_toml_path (str) The path to the cargo.toml manifest 134 | (--manifest) 135 | debug (boolean) Controls whether --debug or --release is 136 | passed to cargo. 137 | extra_carg_args (list) A list of extra argumenents to be passed to 138 | cargo. 139 | quiet (boolean) If True, doesn't echo cargo's output. 140 | Returns: 141 | A Command subclass suitable for passing to the cmdclass argument 142 | of distutils. 143 | """ 144 | 145 | # Manufacture a once-off command class here and set the params on it as 146 | # class members, which it can retrieve later in initialize_options. 147 | # This is clumsy, but distutils doesn't give you an appropriate 148 | # hook for passing params to custom command classes (and it does the 149 | # instantiation). 150 | 151 | _args = locals() 152 | 153 | class RustBuildCommand_Impl(RustBuildCommand): 154 | args = _args 155 | 156 | return RustBuildCommand_Impl 157 | 158 | 159 | class install_lib_including_rust(install_lib): 160 | """ 161 | A replacement install_lib cmdclass that remembers to build_rust 162 | during install_lib. 163 | Typically you want to use this so that the usual 'setup.py install' 164 | just works. 165 | """ 166 | 167 | def build(self): 168 | install_lib.build(self) 169 | if not self.skip_build: 170 | self.run_command('build_rust') 171 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\certitude.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certitude.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/certitude.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certitude.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/certitude" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certitude" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # certitude documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Mar 15 16:45:56 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | 'sphinx.ext.autodoc', 33 | 'sphinx.ext.intersphinx', 34 | 'sphinx.ext.viewcode', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The encoding of source files. 46 | #source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = u'certitude' 53 | copyright = u'2016, Cory Benfield' 54 | author = u'Cory Benfield' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = u'1.0.1' 62 | # The full version, including alpha/beta/rc tags. 63 | release = u'1.0.1' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = None 71 | 72 | # There are two options for replacing |today|: either, you set today to some 73 | # non-false value, then it is used: 74 | #today = '' 75 | # Else, today_fmt is used as the format for a strftime call. 76 | #today_fmt = '%B %d, %Y' 77 | 78 | # List of patterns, relative to source directory, that match files and 79 | # directories to ignore when looking for source files. 80 | exclude_patterns = [] 81 | 82 | # The reST default role (used for this markup: `text`) to use for all 83 | # documents. 84 | #default_role = None 85 | 86 | # If true, '()' will be appended to :func: etc. cross-reference text. 87 | #add_function_parentheses = True 88 | 89 | # If true, the current module name will be prepended to all description 90 | # unit titles (such as .. function::). 91 | #add_module_names = True 92 | 93 | # If true, sectionauthor and moduleauthor directives will be shown in the 94 | # output. They are ignored by default. 95 | #show_authors = False 96 | 97 | # The name of the Pygments (syntax highlighting) style to use. 98 | pygments_style = 'sphinx' 99 | 100 | # A list of ignored prefixes for module index sorting. 101 | #modindex_common_prefix = [] 102 | 103 | # If true, keep warnings as "system message" paragraphs in the built documents. 104 | #keep_warnings = False 105 | 106 | # If true, `todo` and `todoList` produce output, else they produce nothing. 107 | todo_include_todos = False 108 | 109 | 110 | # -- Options for HTML output ---------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'alabaster' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a theme 117 | # further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. 129 | #html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the top 132 | # of the sidebar. 133 | #html_logo = None 134 | 135 | # The name of an image file (within the static path) to use as favicon of the 136 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 137 | # pixels large. 138 | #html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) here, 141 | # relative to this directory. They are copied after the builtin static files, 142 | # so a file named "default.css" will overwrite the builtin "default.css". 143 | html_static_path = ['_static'] 144 | 145 | # Add any extra paths that contain custom files (such as robots.txt or 146 | # .htaccess) here, relative to this directory. These files are copied 147 | # directly to the root of the documentation. 148 | #html_extra_path = [] 149 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 151 | # using the given strftime format. 152 | #html_last_updated_fmt = '%b %d, %Y' 153 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to 155 | # typographically correct entities. 156 | #html_use_smartypants = True 157 | 158 | # Custom sidebar templates, maps document names to template names. 159 | #html_sidebars = {} 160 | 161 | # Additional templates that should be rendered to pages, maps page names to 162 | # template names. 163 | #html_additional_pages = {} 164 | 165 | # If false, no module index is generated. 166 | #html_domain_indices = True 167 | 168 | # If false, no index is generated. 169 | #html_use_index = True 170 | 171 | # If true, the index is split into individual pages for each letter. 172 | #html_split_index = False 173 | 174 | # If true, links to the reST sources are added to the pages. 175 | #html_show_sourcelink = True 176 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 178 | #html_show_sphinx = True 179 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages will 184 | # contain a tag referring to it. The value of this option must be the 185 | # base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Language to be used for generating the HTML full-text search index. 192 | # Sphinx supports the following languages: 193 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 194 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 195 | #html_search_language = 'en' 196 | 197 | # A dictionary with options for the search language support, empty by default. 198 | # Now only 'ja' uses this config value 199 | #html_search_options = {'type': 'default'} 200 | 201 | # The name of a javascript file (relative to the configuration directory) that 202 | # implements a search results scorer. If empty, the default will be used. 203 | #html_search_scorer = 'scorer.js' 204 | 205 | # Output file base name for HTML help builder. 206 | htmlhelp_basename = 'certitudedoc' 207 | 208 | # -- Options for LaTeX output --------------------------------------------- 209 | 210 | latex_elements = { 211 | # The paper size ('letterpaper' or 'a4paper'). 212 | #'papersize': 'letterpaper', 213 | 214 | # The font size ('10pt', '11pt' or '12pt'). 215 | #'pointsize': '10pt', 216 | 217 | # Additional stuff for the LaTeX preamble. 218 | #'preamble': '', 219 | 220 | # Latex figure (float) alignment 221 | #'figure_align': 'htbp', 222 | } 223 | 224 | # Grouping the document tree into LaTeX files. List of tuples 225 | # (source start file, target name, title, 226 | # author, documentclass [howto, manual, or own class]). 227 | latex_documents = [ 228 | (master_doc, 'certitude.tex', u'certitude Documentation', 229 | u'Cory Benfield', 'manual'), 230 | ] 231 | 232 | # The name of an image file (relative to this directory) to place at the top of 233 | # the title page. 234 | #latex_logo = None 235 | 236 | # For "manual" documents, if this is true, then toplevel headings are parts, 237 | # not chapters. 238 | #latex_use_parts = False 239 | 240 | # If true, show page references after internal links. 241 | #latex_show_pagerefs = False 242 | 243 | # If true, show URL addresses after external links. 244 | #latex_show_urls = False 245 | 246 | # Documents to append as an appendix to all manuals. 247 | #latex_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | #latex_domain_indices = True 251 | 252 | 253 | # -- Options for manual page output --------------------------------------- 254 | 255 | # One entry per manual page. List of tuples 256 | # (source start file, name, description, authors, manual section). 257 | man_pages = [ 258 | (master_doc, 'certitude', u'certitude Documentation', 259 | [author], 1) 260 | ] 261 | 262 | # If true, show URL addresses after external links. 263 | #man_show_urls = False 264 | 265 | 266 | # -- Options for Texinfo output ------------------------------------------- 267 | 268 | # Grouping the document tree into Texinfo files. List of tuples 269 | # (source start file, target name, title, author, 270 | # dir menu entry, description, category) 271 | texinfo_documents = [ 272 | (master_doc, 'certitude', u'certitude Documentation', 273 | author, 'certitude', 'One line description of project.', 274 | 'Miscellaneous'), 275 | ] 276 | 277 | # Documents to append as an appendix to all manuals. 278 | #texinfo_appendices = [] 279 | 280 | # If false, no module index is generated. 281 | #texinfo_domain_indices = True 282 | 283 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 284 | #texinfo_show_urls = 'footnote' 285 | 286 | # If true, do not generate a @detailmenu in the "Top" node's menu. 287 | #texinfo_no_detailmenu = False 288 | 289 | 290 | # Example configuration for intersphinx: refer to the Python standard library. 291 | intersphinx_mapping = {'https://docs.python.org/': None} 292 | --------------------------------------------------------------------------------