pax_global_header00006660000000000000000000000064127466646260014535gustar00rootroot0000000000000052 comment=64e7a1f02615e8ec1e7826a097883c480ff73a1c ocspbuilder-0.10.2/000077500000000000000000000000001274666462600141305ustar00rootroot00000000000000ocspbuilder-0.10.2/.gitignore000066400000000000000000000001611274666462600161160ustar00rootroot00000000000000__pycache__/ *.pyc .python-version *.egg-info/ build/ dist/ tests/output/ .DS_Store .coverage .tox .cache/ *.swp ocspbuilder-0.10.2/.travis.yml000066400000000000000000000071051274666462600162440ustar00rootroot00000000000000sudo: false language: c branches: except: - /^[0-9]+\.[0-9]+\.[0-9]$/ matrix: include: - os: osx python: "2.6" env: TRAVIS_PYTHON_VERSION=2.6 - os: osx python: "2.7" env: TRAVIS_PYTHON_VERSION=2.7 - os: osx python: "3.5" env: TRAVIS_PYTHON_VERSION=3.5 - os: osx python: "pypy" env: TRAVIS_PYTHON_VERSION=pypy - os: linux language: python python: "2.6" - os: linux language: python python: "2.7" - os: linux language: python python: "3.2" - os: linux language: python python: "3.3" - os: linux language: python python: "3.4" - os: linux language: python python: "3.5" - os: linux language: python python: "pypy" install: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then brew update; brew install pypy; /usr/local/bin/pip_pypy install flake8; /usr/local/bin/pip_pypy install https://github.com/wbond/asn1crypto/archive/master.zip; /usr/local/bin/pip_pypy install https://github.com/wbond/oscrypto/archive/master.zip; export PYTHON_BIN=/usr/local/bin/pypy; else if [ "$TRAVIS_PYTHON_VERSION" == "3.5" ]; then brew update; brew install python3; /usr/local/bin/pip3 install flake8; /usr/local/bin/pip3 install https://github.com/wbond/asn1crypto/archive/master.zip; /usr/local/bin/pip3 install https://github.com/wbond/oscrypto/archive/master.zip; export PYTHON_BIN=/usr/local/bin/python3; else if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then curl --silent --show-error https://bootstrap.pypa.io/get-pip.py | sudo /usr/bin/python2.7; sudo /usr/bin/python2.7 -W ignore -c "import pip; pip.main(['--disable-pip-version-check', '--quiet', 'install', 'flake8'])"; sudo /usr/bin/python2.7 -W ignore -c "import pip; pip.main(['--disable-pip-version-check', '--quiet', 'install', 'https://github.com/wbond/asn1crypto/archive/master.zip'])"; sudo /usr/bin/python2.7 -W ignore -c "import pip; pip.main(['--disable-pip-version-check', '--quiet', 'install', 'https://github.com/wbond/oscrypto/archive/master.zip'])"; export PYTHON_BIN=/usr/bin/python2.7; else curl --silent --show-error --location -o asn1crypto-master.zip https://github.com/wbond/asn1crypto/archive/master.zip; unzip asn1crypto-master.zip; cd asn1crypto-master; sudo /usr/bin/python2.6 setup.py install clean; cd ..; curl --silent --show-error --location -o oscrypto-master.zip https://github.com/wbond/oscrypto/archive/master.zip; unzip oscrypto-master.zip; cd oscrypto-master; sudo /usr/bin/python2.6 setup.py install clean; cd ..; export PYTHON_BIN=/usr/bin/python2.6; fi fi fi else python -c "import sys,pip; pip.main(['install', '--upgrade', 'pip' + ('==7.1.2' if sys.version_info[0:2] == (3, 2) else '')])"; pip install flake8; pip install https://github.com/wbond/asn1crypto/archive/master.zip; pip install https://github.com/wbond/oscrypto/archive/master.zip; export PYTHON_BIN=python; fi script: - $PYTHON_BIN run.py ci ocspbuilder-0.10.2/LICENSE000066400000000000000000000020561274666462600151400ustar00rootroot00000000000000Copyright (c) 2015 Will Bond 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ocspbuilder-0.10.2/appveyor.yml000066400000000000000000000061101274666462600165160ustar00rootroot00000000000000version: "{build}" skip_tags: true environment: matrix: - PYTHON: "C:\\Python26" PYTHON_ID: "26" PYTHON_EXE: python - PYTHON: "C:\\Python26-x64" PYTHON_ID: "26-x64" PYTHON_EXE: python - PYTHON: "C:\\Python27" PYTHON_ID: "27" PYTHON_EXE: python - PYTHON: "C:\\Python27-x64" PYTHON_ID: "27-x64" PYTHON_EXE: python - PYTHON: "C:\\Python33" PYTHON_ID: "33" PYTHON_EXE: python - PYTHON: "C:\\Python33-x64" PYTHON_ID: "33-x64" PYTHON_EXE: python - PYTHON: "C:\\pypy2-v5.3.1-win32" PYTHON_ID: "pypy" PYTHON_EXE: pypy install: - ps: |- $env:PYTMP = "${env:TMP}\py"; if (!(Test-Path "$env:PYTMP")) { New-Item -ItemType directory -Path "$env:PYTMP" | Out-Null; } if ("${env:PYTHON_ID}" -eq "pypy") { if (!(Test-Path "${env:PYTMP}\pypy2-v5.3.1-win32.zip")) { (New-Object Net.WebClient).DownloadFile('https://bitbucket.org/pypy/pypy/downloads/pypy2-v5.3.1-win32.zip', "${env:PYTMP}\pypy2-v5.3.1-win32.zip"); } 7z x -y "${env:PYTMP}\pypy2-v5.3.1-win32.zip" -oC:\ | Out-Null; if (!(Test-Path "${env:PYTMP}\get-pip.py")) { (New-Object Net.WebClient).DownloadFile('https://bootstrap.pypa.io/get-pip.py', "${env:PYTMP}\get-pip.py"); } & "${env:PYTHON}\pypy.exe" "${env:PYTMP}\get-pip.py"; & "${env:PYTHON}\bin\pip.exe" --disable-pip-version-check --quiet install flake8; & "${env:PYTHON}\bin\pip.exe" --disable-pip-version-check --quiet install https://github.com/wbond/asn1crypto/archive/master.zip; & "${env:PYTHON}\bin\pip.exe" --disable-pip-version-check --quiet install https://github.com/wbond/oscrypto/archive/master.zip; } elseif ("${env:PYTHON_ID}" -eq "26" -or "${env:PYTHON_ID}" -eq "26-x64") { # Skip flake8 for 2.6 since pip, flake8 and pycodestyle have all deprecated support for it (New-Object Net.WebClient).DownloadFile('https://github.com/wbond/asn1crypto/archive/master.zip', "${env:TMP}\asn1crypto-master.zip"); 7z x -y "${env:TMP}\asn1crypto-master.zip" -o"${env:TMP}\" | Out-Null; (New-Object Net.WebClient).DownloadFile('https://github.com/wbond/oscrypto/archive/master.zip', "${env:TMP}\oscrypto-master.zip"); 7z x -y "${env:TMP}\oscrypto-master.zip" -o"${env:TMP}\" | Out-Null; $origDir = "$pwd"; cd "${env:TMP}\asn1crypto-master\"; & "${env:PYTHON}\python.exe" setup.py install clean; cd "${env:TMP}\oscrypto-master\"; & "${env:PYTHON}\python.exe" setup.py install clean; cd "$origDir"; } else { & "${env:PYTHON}\Scripts\pip.exe" --disable-pip-version-check --quiet install flake8; & "${env:PYTHON}\Scripts\pip.exe" --disable-pip-version-check --quiet install https://github.com/wbond/asn1crypto/archive/master.zip; & "${env:PYTHON}\Scripts\pip.exe" --disable-pip-version-check --quiet install https://github.com/wbond/oscrypto/archive/master.zip; } - "SET PATH=%PYTHON%;%PATH%" cache: - '%TMP%\py\' build: off test_script: - cmd: "%PYTHON_EXE% run.py ci" ocspbuilder-0.10.2/changelog.md000066400000000000000000000010421274666462600163760ustar00rootroot00000000000000# changelog ## 0.10.2 - Updated [asn1crypto](https://github.com/wbond/asn1crypto) dependency to `0.18.1`, [oscrypto](https://github.com/wbond/oscrypto) dependency to `0.16.1`. ## 0.10.1 - `OCSPResponseBuilder()` no longer requires the `certificate` and `certificate_status` parameters when the `response_status` is not `"successful"` ## 0.10.0 - Added the options `unknown` and `revoked` to the `certificate_status` parameter of `OCSPResponseBuilder()` ## 0.9.1 - Package metadata updates ## 0.9.0 - Initial release ocspbuilder-0.10.2/dev-requirements.txt000066400000000000000000000000411274666462600201630ustar00rootroot00000000000000coverage>=4.0b1 flake8 CommonMarkocspbuilder-0.10.2/dev/000077500000000000000000000000001274666462600147065ustar00rootroot00000000000000ocspbuilder-0.10.2/dev/__init__.py000066400000000000000000000000001274666462600170050ustar00rootroot00000000000000ocspbuilder-0.10.2/dev/api_docs.py000066400000000000000000000351151274666462600170460ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import re import os import ast import _ast import textwrap import CommonMark from collections import OrderedDict cur_dir = os.path.dirname(__file__) project_dir = os.path.abspath(os.path.join(cur_dir, '..')) docs_dir = os.path.join(project_dir, 'docs') module_name = 'ocspbuilder' # Maps a markdown document to a Python source file to look in for # class/method/function docstrings MD_SOURCE_MAP = { 'docs/api.md': ['ocspbuilder/__init__.py'], } # A search/replace dictionary to modify docstring contents before generating # markdown from them definition_replacements = {} if hasattr(CommonMark, 'DocParser'): raise EnvironmentError("CommonMark must be version 0.6.0 or newer") def _get_func_info(docstring, def_lineno, code_lines, prefix): """ Extracts the function signature and description of a Python function :param docstring: A unicode string of the docstring for the function :param def_lineno: An integer line number that function was defined on :param code_lines: A list of unicode string lines from the source file the function was defined in :param prefix: A prefix to prepend to all output lines :return: A 2-element tuple: - [0] A unicode string of the function signature with a docstring of parameter info - [1] A markdown snippet of the function description """ def_index = def_lineno - 1 definition = code_lines[def_index] definition = definition.rstrip() while not definition.endswith(':'): def_index += 1 definition += '\n' + code_lines[def_index].rstrip() definition = textwrap.dedent(definition).rstrip(':') definition = definition.replace('\n', '\n' + prefix) description = '' found_colon = False params = '' for line in docstring.splitlines(): if line and line[0] == ':': found_colon = True if not found_colon: if description: description += '\n' description += line else: if params: params += '\n' params += line description = description.strip() description_md = '' if description: description_md = '%s%s' % (prefix, description.replace('\n', '\n' + prefix)) description_md = re.sub('\n>(\\s+)\n', '\n>\n', description_md) params = params.strip() if params: definition += (':\n%s """\n%s ' % (prefix, prefix)) definition += params.replace('\n', '\n%s ' % prefix) definition += ('\n%s """' % prefix) definition = re.sub('\n>(\\s+)\n', '\n>\n', definition) for search, replace in definition_replacements.items(): definition = definition.replace(search, replace) return (definition, description_md) def _find_sections(md_ast, sections, last, last_class, total_lines=None): """ Walks through a CommonMark AST to find section headers that delineate content that should be updated by this script :param md_ast: The AST of the markdown document :param sections: A dict to store the start and end lines of a section. The key will be a two-element tuple of the section type ("class", "function", "method" or "attribute") and identifier. The values are a two-element tuple of the start and end line number in the markdown document of the section. :param last: A dict containing information about the last section header seen. Includes the keys "type_name", "identifier", "start_line". :param last_class: A unicode string of the name of the last class found - used when processing methods and attributes. :param total_lines: An integer of the total number of lines in the markdown document - used to work around a bug in the API of the Python port of CommonMark """ def child_walker(node): for child, entering in node.walker(): if child == node: continue yield child, entering for child, entering in child_walker(md_ast): if child.t == 'heading': start_line = child.sourcepos[0][0] if child.level == 2: if last: sections[(last['type_name'], last['identifier'])] = (last['start_line'], start_line - 1) last.clear() if child.level in set([3, 5]): heading_elements = [] for heading_child, _ in child_walker(child): heading_elements.append(heading_child) if len(heading_elements) != 2: continue first = heading_elements[0] second = heading_elements[1] if first.t != 'code': continue if second.t != 'text': continue type_name = second.literal.strip() identifier = first.literal.strip().replace('()', '').lstrip('.') if last: sections[(last['type_name'], last['identifier'])] = (last['start_line'], start_line - 1) last.clear() if type_name == 'function': if child.level != 3: continue if type_name == 'class': if child.level != 3: continue last_class.append(identifier) if type_name in set(['method', 'attribute']): if child.level != 5: continue identifier = last_class[-1] + '.' + identifier last.update({ 'type_name': type_name, 'identifier': identifier, 'start_line': start_line, }) elif child.t == 'block_quote': find_sections(child, sections, last, last_class) if last: sections[(last['type_name'], last['identifier'])] = (last['start_line'], total_lines) find_sections = _find_sections def walk_ast(node, code_lines, sections, md_chunks): """ A callback used to walk the Python AST looking for classes, functions, methods and attributes. Generates chunks of markdown markup to replace the existing content. :param node: An _ast module node object :param code_lines: A list of unicode strings - the source lines of the Python file :param sections: A dict of markdown document sections that need to be updated. The key will be a two-element tuple of the section type ("class", "function", "method" or "attribute") and identifier. The values are a two-element tuple of the start and end line number in the markdown document of the section. :param md_chunks: A dict with keys from the sections param and the values being a unicode string containing a chunk of markdown markup. """ if isinstance(node, _ast.FunctionDef): key = ('function', node.name) if key not in sections: return docstring = ast.get_docstring(node) def_lineno = node.lineno + len(node.decorator_list) definition, description_md = _get_func_info(docstring, def_lineno, code_lines, '> ') md_chunk = textwrap.dedent(""" ### `%s()` function > ```python > %s > ``` > %s """).strip() % ( node.name, definition, description_md ) + "\n" md_chunks[key] = md_chunk elif isinstance(node, _ast.ClassDef): if ('class', node.name) not in sections: return for subnode in node.body: if isinstance(subnode, _ast.FunctionDef): node_id = node.name + '.' + subnode.name method_key = ('method', node_id) is_method = method_key in sections attribute_key = ('attribute', node_id) is_attribute = attribute_key in sections is_constructor = subnode.name == '__init__' if not is_constructor and not is_attribute and not is_method: continue docstring = ast.get_docstring(subnode) def_lineno = subnode.lineno + len(subnode.decorator_list) if not docstring: continue if is_method or is_constructor: definition, description_md = _get_func_info(docstring, def_lineno, code_lines, '> > ') if is_constructor: key = ('class', node.name) class_docstring = ast.get_docstring(node) or '' class_description = textwrap.dedent(class_docstring).strip() if class_description: class_description_md = "> %s\n>" % (class_description.replace("\n", "\n> ")) else: class_description_md = '' md_chunk = textwrap.dedent(""" ### `%s()` class %s > ##### constructor > > > ```python > > %s > > ``` > > %s """).strip() % ( node.name, class_description_md, definition, description_md ) md_chunk = md_chunk.replace('\n\n\n', '\n\n') else: key = method_key md_chunk = textwrap.dedent(""" > > ##### `.%s()` method > > > ```python > > %s > > ``` > > %s """).strip() % ( subnode.name, definition, description_md ) if md_chunk[-5:] == '\n> >\n': md_chunk = md_chunk[0:-5] else: key = attribute_key description = textwrap.dedent(docstring).strip() description_md = "> > %s" % (description.replace("\n", "\n> > ")) md_chunk = textwrap.dedent(""" > > ##### `.%s` attribute > %s """).strip() % ( subnode.name, description_md ) md_chunks[key] = re.sub('[ \\t]+\n', '\n', md_chunk.rstrip()) elif isinstance(node, _ast.If): for subast in node.body: walk_ast(subast, code_lines, sections, md_chunks) for subast in node.orelse: walk_ast(subast, code_lines, sections, md_chunks) def run(): """ Looks through the docs/ dir and parses each markdown document, looking for sections to update from Python docstrings. Looks for section headers in the format: - ### `ClassName()` class - ##### `.method_name()` method - ##### `.attribute_name` attribute - ### `function_name()` function The markdown content following these section headers up until the next section header will be replaced by new markdown generated from the Python docstrings of the associated source files. By default maps docs/{name}.md to {modulename}/{name}.py. Allows for custom mapping via the MD_SOURCE_MAP variable. """ print('Updating API docs...') md_files = [] for root, _, filenames in os.walk(docs_dir): for filename in filenames: if not filename.endswith('.md'): continue md_files.append(os.path.join(root, filename)) parser = CommonMark.Parser() for md_file in md_files: md_file_relative = md_file[len(project_dir) + 1:] if md_file_relative in MD_SOURCE_MAP: py_files = MD_SOURCE_MAP[md_file_relative] py_paths = [os.path.join(project_dir, py_file) for py_file in py_files] else: py_files = [os.path.basename(md_file).replace('.md', '.py')] py_paths = [os.path.join(project_dir, module_name, py_files[0])] if not os.path.exists(py_paths[0]): continue with open(md_file, 'rb') as f: markdown = f.read().decode('utf-8') original_markdown = markdown md_lines = list(markdown.splitlines()) md_ast = parser.parse(markdown) last_class = [] last = {} sections = OrderedDict() find_sections(md_ast, sections, last, last_class, markdown.count("\n") + 1) md_chunks = {} for index, py_file in enumerate(py_files): py_path = py_paths[index] with open(os.path.join(py_path), 'rb') as f: code = f.read().decode('utf-8') module_ast = ast.parse(code, filename=py_file) code_lines = list(code.splitlines()) for node in ast.iter_child_nodes(module_ast): walk_ast(node, code_lines, sections, md_chunks) added_lines = 0 def _replace_md(key, sections, md_chunk, md_lines, added_lines): start, end = sections[key] start -= 1 start += added_lines end += added_lines new_lines = md_chunk.split('\n') added_lines += len(new_lines) - (end - start) # Ensure a newline above each class header if start > 0 and md_lines[start][0:4] == '### ' and md_lines[start - 1][0:1] == '>': added_lines += 1 new_lines.insert(0, '') md_lines[start:end] = new_lines return added_lines for key in sections: if key not in md_chunks: raise ValueError('No documentation found for %s' % key[1]) added_lines = _replace_md(key, sections, md_chunks[key], md_lines, added_lines) markdown = '\n'.join(md_lines).strip() + '\n' if original_markdown != markdown: with open(md_file, 'wb') as f: f.write(markdown.encode('utf-8')) if __name__ == '__main__': run() ocspbuilder-0.10.2/dev/ci.py000066400000000000000000000012111274666462600156460ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import sys from .tests import run as run_tests if sys.version_info >= (2, 7): from .lint import run as run_lint def run(): """ Runs the linter and tests :return: A bool - if the linter and tests ran successfully """ print('Python ' + sys.version.replace('\n', '')) if sys.version_info >= (2, 7): print('') lint_result = run_lint() else: lint_result = True print('\nRunning tests') sys.stdout.flush() tests_result = run_tests() return lint_result and tests_result ocspbuilder-0.10.2/dev/coverage.py000066400000000000000000000007471274666462600170630ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import coverage def run(): """ Runs the tests while measuring coverage :return: A bool - if the tests ran successfully """ cov = coverage.Coverage(include='ocspbuilder/*.py') cov.start() from .tests import run as run_tests result = run_tests() print() cov.stop() cov.save() cov.report(show_missing=False) return result ocspbuilder-0.10.2/dev/lint.py000066400000000000000000000016371274666462600162350ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import os import flake8 if flake8.__version_info__ < (3,): from flake8.engine import get_style_guide else: from flake8.api.legacy import get_style_guide cur_dir = os.path.dirname(__file__) config_file = os.path.join(cur_dir, '..', 'tox.ini') def run(): """ Runs flake8 lint :return: A bool - if flake8 did not find any errors """ print('Running flake8') flake8_style = get_style_guide(config_file=config_file) paths = [] for root, _, filenames in os.walk('ocspbuilder'): for filename in filenames: if not filename.endswith('.py'): continue paths.append(os.path.join(root, filename)) report = flake8_style.check_files(paths) success = report.total_errors == 0 if success: print('OK') return success ocspbuilder-0.10.2/dev/release.py000066400000000000000000000033431274666462600167030ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import os import subprocess import sys import setuptools.sandbox import twine.cli base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) setup_file = os.path.join(base_dir, 'setup.py') def run(): """ Creates a sdist .tar.gz and a bdist_wheel --univeral .whl and uploads them to pypi :return: A bool - if the packaging and upload process was successful """ git_wc_proc = subprocess.Popen( ['git', 'status', '--porcelain', '-uno'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=base_dir ) git_wc_status, _ = git_wc_proc.communicate() if len(git_wc_status) > 0: print(git_wc_status.decode('utf-8').rstrip(), file=sys.stderr) print('Unable to perform release since working copy is not clean', file=sys.stderr) return False git_tag_proc = subprocess.Popen( ['git', 'tag', '-l', '--contains', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=base_dir ) tag, tag_error = git_tag_proc.communicate() if len(tag_error) > 0: print(tag_error.decode('utf-8').rstrip(), file=sys.stderr) print('Error looking for current git tag', file=sys.stderr) return False if len(tag) == 0: print('No git tag found on HEAD', file=sys.stderr) return False tag = tag.decode('ascii').strip() setuptools.sandbox.run_setup( setup_file, ['sdist', 'bdist_wheel', '--universal'] ) twine.cli.dispatch(['upload', 'dist/ocspbuilder-%s*' % tag]) setuptools.sandbox.run_setup( setup_file, ['clean'] ) ocspbuilder-0.10.2/dev/tests.py000066400000000000000000000027731274666462600164330ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import unittest import re from tests.test_ocsp_response_builder import OCSPResponseBuilderTests from tests.test_ocsp_request_builder import OCSPRequestBuilderTests test_classes = [OCSPResponseBuilderTests, OCSPRequestBuilderTests] def make_suite(): """ Constructs a unittest.TestSuite() of all tests for the package. For use with setuptools. :return: A unittest.TestSuite() object """ loader = unittest.TestLoader() suite = unittest.TestSuite() for test_class in test_classes: tests = loader.loadTestsFromTestCase(test_class) suite.addTests(tests) return suite def run(matcher=None): """ Runs the tests :param matcher: A unicode string containing a regular expression to use to filter test names by. A value of None will cause no filtering. :return: A bool - if the tests succeeded """ suite = unittest.TestSuite() loader = unittest.TestLoader() for test_class in test_classes: if matcher: names = loader.getTestCaseNames(test_class) for name in names: if re.search(matcher, name): suite.addTest(test_class(name)) else: suite.addTest(loader.loadTestsFromTestCase(test_class)) verbosity = 2 if matcher else 1 result = unittest.TextTestRunner(verbosity=verbosity).run(suite) return result.wasSuccessful() ocspbuilder-0.10.2/docs/000077500000000000000000000000001274666462600150605ustar00rootroot00000000000000ocspbuilder-0.10.2/docs/api.md000066400000000000000000000254701274666462600161630ustar00rootroot00000000000000# ocspbuilder API Documentation - [`OCSPRequestBuilder()`](#ocsprequestbuilder-class) - [`OCSPResponseBuilder()`](#ocspresponsebuilder-class) ### `OCSPRequestBuilder()` class > ##### constructor > > > ```python > > def __init__(self, certificate, issuer): > > """ > > :param certificate: > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate > > object to create the request for > > > > :param issuer: > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate > > object for the issuer of the certificate > > """ > > ``` > > ##### `.certificate` attribute > > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object > > of the certificate to create the request for. > > ##### `.issuer` attribute > > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object > > of the issuer. > > ##### `.hash_algo` attribute > > > A unicode string of the hash algorithm to use when signing the > > request - "sha1", "sha256" (default) or "sha512". > > ##### `.key_hash_algo` attribute > > > A unicode string of the hash algorithm to use when creating the > > certificate identifier - "sha1" (default), or "sha256". > > ##### `.nonce` attribute > > > A bool - if the nonce extension should be used to prevent replay > > attacks. > > ##### `.set_extension()` method > > > ```python > > def set_extension(self, name, value): > > """ > > :param name: > > A unicode string of an extension id name from > > asn1crypto.ocsp.TBSRequestExtensionId or > > asn1crypto.ocsp.RequestExtensionId. If the extension is not one > > defined in those classes, this must be an instance of one of the > > classes instead of a unicode string. > > > > :param value: > > A value object per the specs defined by > > asn1crypto.ocsp.TBSRequestExtension or > > asn1crypto.ocsp.RequestExtension > > """ > > ``` > > > > Sets the value for an extension using a fully constructed > > asn1crypto.core.Asn1Value object. Normally this should not be needed, > > and the convenience attributes should be sufficient. > > > > See the definition of asn1crypto.ocsp.TBSRequestExtension and > > asn1crypto.ocsp.RequestExtension to determine the appropriate object > > type for a given extension. Extensions are marked as critical when RFC > > 6960 indicates so. > > ##### `.build()` method > > > ```python > > def build(self, requestor_private_key=None, requestor_certificate=None, other_certificates=None): > > """ > > :param requestor_private_key: > > An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey > > object for the private key to sign the request with > > > > :param requestor_certificate: > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate > > object of the certificate associated with the private key > > > > :param other_certificates: > > A list of asn1crypto.x509.Certificate or > > oscrypto.asymmetric.Certificate objects that may be useful for the > > OCSP server to verify the request signature. Intermediate > > certificates would be specified here. > > > > :return: > > An asn1crypto.ocsp.OCSPRequest object of the request > > """ > > ``` > > > > Validates the request information, constructs the ASN.1 structure and > > then optionally signs it. > > > > The requestor_private_key, requestor_certificate and other_certificates > > params are all optional and only necessary if the request needs to be > > signed. Signing a request is uncommon for OCSP requests related to web > > TLS connections. ### `OCSPResponseBuilder()` class > ##### constructor > > > ```python > > def __init__(self, response_status, certificate=None, certificate_status=None, revocation_date=None): > > """ > > :param response_status: > > A unicode string of OCSP response type: > > > > - "successful" - when the response includes information about the certificate > > - "malformed_request" - when the request could not be understood > > - "internal_error" - when an internal error occured with the OCSP responder > > - "try_later" - when the OCSP responder is temporarily unavailable > > - "sign_required" - when the OCSP request must be signed > > - "unauthorized" - when the responder is not the correct responder for the certificate > > > > :param certificate: > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate > > object of the certificate the response is about. Only required if > > the response_status is "successful". > > > > :param certificate_status: > > A unicode string of the status of the certificate. Only required if > > the response_status is "successful". > > > > - "good" - when the certificate is in good standing > > - "revoked" - when the certificate is revoked without a reason code > > - "key_compromise" - when a private key is compromised > > - "ca_compromise" - when the CA issuing the certificate is compromised > > - "affiliation_changed" - when the certificate subject name changed > > - "superseded" - when the certificate was replaced with a new one > > - "cessation_of_operation" - when the certificate is no longer needed > > - "certificate_hold" - when the certificate is temporarily invalid > > - "remove_from_crl" - only delta CRLs - when temporary hold is removed > > - "privilege_withdrawn" - one of the usages for a certificate was removed > > - "unknown" - the responder doesn't know about the certificate being requested > > > > :param revocation_date: > > A datetime.datetime object of when the certificate was revoked, if > > the response_status is "successful" and the certificate status is > > not "good" or "unknown". > > """ > > ``` > > > > Unless changed, responses will use SHA-256 for the signature, > > and will be valid from the moment created for one week. > > ##### `.response_status` attribute > > > The overall status of the response. Only a "successful" response will > > include information about the certificate. Other response types are for > > signaling info about the OCSP responder. Valid values include: > > > > - "successful" - when the response includes information about the certificate > > - "malformed_request" - when the request could not be understood > > - "internal_error" - when an internal error occured with the OCSP responder > > - "try_later" - when the OCSP responder is temporarily unavailable > > - "sign_required" - when the OCSP request must be signed > > - "unauthorized" - when the responder is not the correct responder for the certificate > > ##### `.certificate` attribute > > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object > > of the certificate the response is about. > > ##### `.certificate_status` attribute > > > A unicode string of the status of the certificate. Valid values include: > > > > - "good" - when the certificate is in good standing > > - "revoked" - when the certificate is revoked without a reason code > > - "key_compromise" - when a private key is compromised > > - "ca_compromise" - when the CA issuing the certificate is compromised > > - "affiliation_changed" - when the certificate subject name changed > > - "superseded" - when the certificate was replaced with a new one > > - "cessation_of_operation" - when the certificate is no longer needed > > - "certificate_hold" - when the certificate is temporarily invalid > > - "remove_from_crl" - only delta CRLs - when temporary hold is removed > > - "privilege_withdrawn" - one of the usages for a certificate was removed > > - "unknown" - when the responder doesn't know about the certificate being requested > > ##### `.revocation_date` attribute > > > A datetime.datetime object of when the certificate was revoked, if the > > status is not "good" or "unknown". > > ##### `.certificate_issuer` attribute > > > An asn1crypto.x509.Certificate object of the issuer of the certificate. > > This should only be set if the OCSP responder is not the issuer of > > the certificate, but instead a special certificate only for OCSP > > responses. > > ##### `.hash_algo` attribute > > > A unicode string of the hash algorithm to use when signing the > > request - "sha1", "sha256" (default) or "sha512". > > ##### `.key_hash_algo` attribute > > > A unicode string of the hash algorithm to use when creating the > > certificate identifier - "sha1" (default), or "sha256". > > ##### `.nonce` attribute > > > The nonce that was provided during the request. > > ##### `.this_update` attribute > > > A datetime.datetime object of when the response was generated. > > ##### `.next_update` attribute > > > A datetime.datetime object of when the response may next change. This > > should only be set if responses are cached. If responses are generated > > fresh on every request, this should not be set. > > ##### `.set_extension()` method > > > ```python > > def set_extension(self, name, value): > > """ > > :param name: > > A unicode string of an extension id name from > > asn1crypto.ocsp.SingleResponseExtensionId or > > asn1crypto.ocsp.ResponseDataExtensionId. If the extension is not one > > defined in those classes, this must be an instance of one of the > > classes instead of a unicode string. > > > > :param value: > > A value object per the specs defined by > > asn1crypto.ocsp.SingleResponseExtension or > > asn1crypto.ocsp.ResponseDataExtension > > """ > > ``` > > > > Sets the value for an extension using a fully constructed > > asn1crypto.core.Asn1Value object. Normally this should not be needed, > > and the convenience attributes should be sufficient. > > > > See the definition of asn1crypto.ocsp.SingleResponseExtension and > > asn1crypto.ocsp.ResponseDataExtension to determine the appropriate > > object type for a given extension. Extensions are marked as critical > > when RFC 6960 indicates so. > > ##### `.build()` method > > > ```python > > def build(self, responder_private_key=None, responder_certificate=None): > > """ > > :param responder_private_key: > > An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey > > object for the private key to sign the response with > > > > :param responder_certificate: > > An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate > > object of the certificate associated with the private key > > > > :return: > > An asn1crypto.ocsp.OCSPResponse object of the response > > """ > > ``` > > > > Validates the request information, constructs the ASN.1 structure and > > signs it. > > > > The responder_private_key and responder_certificate parameters are only > > required if the response_status is "successful". ocspbuilder-0.10.2/docs/readme.md000066400000000000000000000061361274666462600166450ustar00rootroot00000000000000# ocspbuilder Documentation *ocspbuilder* is a Python library for constructing OCSP requests and responses. It provides a high-level interface with knowledge of RFC 6960 to produce, valid, correct OCSP messages without terrible APIs or hunting through RFCs. Since its only dependencies are the [*asn1crypto*](https://github.com/wbond/asn1crypto#readme) and [*oscrypto*](https://github.com/wbond/oscrypto#readme) libraries, it is easy to install and use on Windows, OS X, Linux and the BSDs. The documentation consists of the following topics: - [Generating a Request](#generating-a-request) - [Constructing a Response](#constructing-a-response) - [API Documentation](api.md) ## Generating a Request A basic OCSP request requires the certificate to obtain the status of, and the issuer certificate: ```python from oscrypto import asymmetric from ocspbuilder import OCSPRequestBuilder subject_cert = asymmetric.load_certificate('/path/to/certificate.crt') issuer_cert = asymmetric.load_certificate('/path/to/issuer.crt') builder = OCSPRequestBuilder(subject_cert, issuer_cert) ocsp_request = builder.build() with open('/path/to/cached_request.der', 'wb') as f: f.write(ocsp_request.dump()) ``` ## Constructing a Response To construct a OCSP response, a few pieces of information are necessary: - subject certificate - certificate status - revocation date and reason (if revoked) - issuer certificate and key, or purpose-created OCSP responder certificate and key The following code shows examples of constructing the response for a certificate in good standing, a revoked certificate and finally a response from an OCSP responder certificate, instead of the certificate issuer. ```python from datetime import datetime from asn1crypto.util import timezone from oscrypto import asymmetric from ocspbuilder import OCSPResponseBuilder subject_cert = asymmetric.load_certificate('/path/to/certificate.crt') issuer_cert = asymmetric.load_certificate('/path/to/issuer.crt') issuer_key = asymmetric.load_private_key('/path/to/issuer.key') # A response for a certificate in good standing builder = OCSPResponseBuilder('successful', subject_cert, 'good') ocsp_response = builder.build(issuer_key, issuer_cert) with open('/path/to/cached_response.der', 'wb') as f: f.write(ocsp_response.dump()) # A response for a certificate that has been revoked revocation_date = datetime(2015, 10, 20, 12, 0, 0, tzinfo=timezone.utc) builder = OCSPResponseBuilder('successful', subject_cert, 'key_compromise', revocation_date) ocsp_response = builder.build(issuer_key, issuer_cert) with open('/path/to/cached_revoked_response.der', 'wb') as f: f.write(ocsp_response.dump()) # A response from a special OCSP response certificate/key responder_cert = asymmetric.load_certificate('/path/to/responder.crt') responder_key = asymmetric.load_private_key('/path/to/responder.key') builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate_issuer = issuer_cert ocsp_response = builder.build(responder_key, responder_cert) with open('/path/to/cached_responder_response.der', 'wb') as f: f.write(ocsp_response.dump()) ``` ocspbuilder-0.10.2/ocspbuilder/000077500000000000000000000000001274666462600164435ustar00rootroot00000000000000ocspbuilder-0.10.2/ocspbuilder/__init__.py000066400000000000000000001146701274666462600205650ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function from datetime import datetime, timedelta import inspect import re import sys import textwrap from asn1crypto import x509, keys, core, ocsp from asn1crypto.util import timezone from oscrypto import asymmetric, util if sys.version_info < (3,): int_types = (int, long) # noqa str_cls = unicode # noqa byte_cls = str else: int_types = (int,) str_cls = str byte_cls = bytes __version__ = '0.10.2' __version_info__ = (0, 10, 2) def _writer(func): """ Decorator for a custom writer, but a default reader """ name = func.__name__ return property(fget=lambda self: getattr(self, '_%s' % name), fset=func) class OCSPRequestBuilder(object): _certificate = None _issuer = None _hash_algo = None _key_hash_algo = None _nonce = True _request_extensions = None _tbs_request_extensions = None def __init__(self, certificate, issuer): """ :param certificate: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object to create the request for :param issuer: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object for the issuer of the certificate """ self.certificate = certificate self.issuer = issuer self._key_hash_algo = 'sha1' self._hash_algo = 'sha256' self._request_extensions = {} self._tbs_request_extensions = {} @_writer def certificate(self, value): """ An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate to create the request for. """ is_oscrypto = isinstance(value, asymmetric.Certificate) if not is_oscrypto and not isinstance(value, x509.Certificate): raise TypeError(_pretty_message( ''' certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(value) )) if is_oscrypto: value = value.asn1 self._certificate = value @_writer def issuer(self, value): """ An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the issuer. """ is_oscrypto = isinstance(value, asymmetric.Certificate) if not is_oscrypto and not isinstance(value, x509.Certificate): raise TypeError(_pretty_message( ''' issuer must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(value) )) if is_oscrypto: value = value.asn1 self._issuer = value @_writer def hash_algo(self, value): """ A unicode string of the hash algorithm to use when signing the request - "sha1", "sha256" (default) or "sha512". """ if value not in set(['sha1', 'sha256', 'sha512']): raise ValueError(_pretty_message( ''' hash_algo must be one of "sha1", "sha256", "sha512", not %s ''', repr(value) )) self._hash_algo = value @_writer def key_hash_algo(self, value): """ A unicode string of the hash algorithm to use when creating the certificate identifier - "sha1" (default), or "sha256". """ if value not in set(['sha1', 'sha256']): raise ValueError(_pretty_message( ''' hash_algo must be one of "sha1", "sha256", not %s ''', repr(value) )) self._key_hash_algo = value @_writer def nonce(self, value): """ A bool - if the nonce extension should be used to prevent replay attacks. """ if not isinstance(value, bool): raise TypeError(_pretty_message( ''' nonce must be a boolean, not %s ''', _type_name(value) )) self._nonce = value def set_extension(self, name, value): """ Sets the value for an extension using a fully constructed asn1crypto.core.Asn1Value object. Normally this should not be needed, and the convenience attributes should be sufficient. See the definition of asn1crypto.ocsp.TBSRequestExtension and asn1crypto.ocsp.RequestExtension to determine the appropriate object type for a given extension. Extensions are marked as critical when RFC 6960 indicates so. :param name: A unicode string of an extension id name from asn1crypto.ocsp.TBSRequestExtensionId or asn1crypto.ocsp.RequestExtensionId. If the extension is not one defined in those classes, this must be an instance of one of the classes instead of a unicode string. :param value: A value object per the specs defined by asn1crypto.ocsp.TBSRequestExtension or asn1crypto.ocsp.RequestExtension """ if isinstance(name, str_cls): request_extension_oids = set([ 'service_locator', '1.3.6.1.5.5.7.48.1.7' ]) tbs_request_extension_oids = set([ 'nonce', 'acceptable_responses', 'preferred_signature_algorithms', '1.3.6.1.5.5.7.48.1.2', '1.3.6.1.5.5.7.48.1.4', '1.3.6.1.5.5.7.48.1.8' ]) if name in request_extension_oids: name = ocsp.RequestExtensionId(name) elif name in tbs_request_extension_oids: name = ocsp.TBSRequestExtensionId(name) else: raise ValueError(_pretty_message( ''' name must be a unicode string from asn1crypto.ocsp.TBSRequestExtensionId or asn1crypto.ocsp.RequestExtensionId, not %s ''', repr(name) )) if isinstance(name, ocsp.RequestExtensionId): extension = ocsp.RequestExtension({'extn_id': name}) elif isinstance(name, ocsp.TBSRequestExtensionId): extension = ocsp.TBSRequestExtension({'extn_id': name}) else: raise TypeError(_pretty_message( ''' name must be a unicode string or an instance of asn1crypto.ocsp.TBSRequestExtensionId or asn1crypto.ocsp.RequestExtensionId, not %s ''', _type_name(name) )) # We use native here to convert OIDs to meaningful names name = extension['extn_id'].native spec = extension.spec('extn_value') if not isinstance(value, spec) and value is not None: raise TypeError(_pretty_message( ''' value must be an instance of %s, not %s ''', _type_name(spec), _type_name(value) )) if isinstance(extension, ocsp.TBSRequestExtension): extn_dict = self._tbs_request_extensions else: extn_dict = self._request_extensions if value is None: if name in extn_dict: del extn_dict[name] else: extn_dict[name] = value def build(self, requestor_private_key=None, requestor_certificate=None, other_certificates=None): """ Validates the request information, constructs the ASN.1 structure and then optionally signs it. The requestor_private_key, requestor_certificate and other_certificates params are all optional and only necessary if the request needs to be signed. Signing a request is uncommon for OCSP requests related to web TLS connections. :param requestor_private_key: An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey object for the private key to sign the request with :param requestor_certificate: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate associated with the private key :param other_certificates: A list of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate objects that may be useful for the OCSP server to verify the request signature. Intermediate certificates would be specified here. :return: An asn1crypto.ocsp.OCSPRequest object of the request """ def _make_extension(name, value): return { 'extn_id': name, 'critical': False, 'extn_value': value } tbs_request_extensions = [] request_extensions = [] has_nonce = False for name, value in self._tbs_request_extensions.items(): if name == 'nonce': has_nonce = True tbs_request_extensions.append(_make_extension(name, value)) if self._nonce and not has_nonce: tbs_request_extensions.append( _make_extension('nonce', util.rand_bytes(16)) ) if not tbs_request_extensions: tbs_request_extensions = None for name, value in self._request_extensions.items(): request_extensions.append(_make_extension(name, value)) if not request_extensions: request_extensions = None tbs_request = ocsp.TBSRequest({ 'request_list': [ { 'req_cert': { 'hash_algorithm': { 'algorithm': self._key_hash_algo }, 'issuer_name_hash': getattr(self._certificate.issuer, self._key_hash_algo), 'issuer_key_hash': getattr(self._issuer.public_key, self._key_hash_algo), 'serial_number': self._certificate.serial_number, }, 'single_request_extensions': request_extensions } ], 'request_extensions': tbs_request_extensions }) signature = None if requestor_private_key or requestor_certificate or other_certificates: is_oscrypto = isinstance(requestor_private_key, asymmetric.PrivateKey) if not isinstance(requestor_private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError(_pretty_message( ''' requestor_private_key must be an instance of asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey, not %s ''', _type_name(requestor_private_key) )) cert_is_oscrypto = isinstance(requestor_certificate, asymmetric.Certificate) if not isinstance(requestor_certificate, x509.Certificate) and not cert_is_oscrypto: raise TypeError(_pretty_message( ''' requestor_certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(requestor_certificate) )) if other_certificates is not None and not isinstance(other_certificates, list): raise TypeError(_pretty_message( ''' other_certificates must be a list of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate objects, not %s ''', _type_name(other_certificates) )) if cert_is_oscrypto: requestor_certificate = requestor_certificate.asn1 tbs_request['requestor_name'] = x509.GeneralName( name='directory_name', value=requestor_certificate.subject ) certificates = [requestor_certificate] for other_certificate in other_certificates: other_cert_is_oscrypto = isinstance(other_certificate, asymmetric.Certificate) if not isinstance(other_certificate, x509.Certificate) and not other_cert_is_oscrypto: raise TypeError(_pretty_message( ''' other_certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(other_certificate) )) if other_cert_is_oscrypto: other_certificate = other_certificate.asn1 certificates.append(other_certificate) signature_algo = requestor_private_key.algorithm if signature_algo == 'ec': signature_algo = 'ecdsa' signature_algorithm_id = '%s_%s' % (self._hash_algo, signature_algo) if requestor_private_key.algorithm == 'rsa': sign_func = asymmetric.rsa_pkcs1v15_sign elif requestor_private_key.algorithm == 'dsa': sign_func = asymmetric.dsa_sign elif requestor_private_key.algorithm == 'ec': sign_func = asymmetric.ecdsa_sign if not is_oscrypto: requestor_private_key = asymmetric.load_private_key(requestor_private_key) signature_bytes = sign_func(requestor_private_key, tbs_request.dump(), self._hash_algo) signature = ocsp.Signature({ 'signature_algorithm': {'algorithm': signature_algorithm_id}, 'signature': signature_bytes, 'certs': certificates }) return ocsp.OCSPRequest({ 'tbs_request': tbs_request, 'optional_signature': signature }) class OCSPResponseBuilder(object): _response_status = None _certificate = None _certificate_status = None _revocation_date = None _certificate_issuer = None _hash_algo = None _key_hash_algo = None _nonce = None _this_update = None _next_update = None _response_data_extensions = None _single_response_extensions = None def __init__(self, response_status, certificate=None, certificate_status=None, revocation_date=None): """ Unless changed, responses will use SHA-256 for the signature, and will be valid from the moment created for one week. :param response_status: A unicode string of OCSP response type: - "successful" - when the response includes information about the certificate - "malformed_request" - when the request could not be understood - "internal_error" - when an internal error occured with the OCSP responder - "try_later" - when the OCSP responder is temporarily unavailable - "sign_required" - when the OCSP request must be signed - "unauthorized" - when the responder is not the correct responder for the certificate :param certificate: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate the response is about. Only required if the response_status is "successful". :param certificate_status: A unicode string of the status of the certificate. Only required if the response_status is "successful". - "good" - when the certificate is in good standing - "revoked" - when the certificate is revoked without a reason code - "key_compromise" - when a private key is compromised - "ca_compromise" - when the CA issuing the certificate is compromised - "affiliation_changed" - when the certificate subject name changed - "superseded" - when the certificate was replaced with a new one - "cessation_of_operation" - when the certificate is no longer needed - "certificate_hold" - when the certificate is temporarily invalid - "remove_from_crl" - only delta CRLs - when temporary hold is removed - "privilege_withdrawn" - one of the usages for a certificate was removed - "unknown" - the responder doesn't know about the certificate being requested :param revocation_date: A datetime.datetime object of when the certificate was revoked, if the response_status is "successful" and the certificate status is not "good" or "unknown". """ self.response_status = response_status self.certificate = certificate self.certificate_status = certificate_status self.revocation_date = revocation_date self._key_hash_algo = 'sha1' self._hash_algo = 'sha256' self._response_data_extensions = {} self._single_response_extensions = {} @_writer def response_status(self, value): """ The overall status of the response. Only a "successful" response will include information about the certificate. Other response types are for signaling info about the OCSP responder. Valid values include: - "successful" - when the response includes information about the certificate - "malformed_request" - when the request could not be understood - "internal_error" - when an internal error occured with the OCSP responder - "try_later" - when the OCSP responder is temporarily unavailable - "sign_required" - when the OCSP request must be signed - "unauthorized" - when the responder is not the correct responder for the certificate """ if not isinstance(value, str_cls): raise TypeError(_pretty_message( ''' response_status must be a unicode string, not %s ''', _type_name(value) )) valid_response_statuses = set([ 'successful', 'malformed_request', 'internal_error', 'try_later', 'sign_required', 'unauthorized' ]) if value not in valid_response_statuses: raise ValueError(_pretty_message( ''' response_status must be one of "successful", "malformed_request", "internal_error", "try_later", "sign_required", "unauthorized", not %s ''', repr(value) )) self._response_status = value @_writer def certificate(self, value): """ An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate the response is about. """ if value is not None: is_oscrypto = isinstance(value, asymmetric.Certificate) if not is_oscrypto and not isinstance(value, x509.Certificate): raise TypeError(_pretty_message( ''' certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(value) )) if is_oscrypto: value = value.asn1 self._certificate = value @_writer def certificate_status(self, value): """ A unicode string of the status of the certificate. Valid values include: - "good" - when the certificate is in good standing - "revoked" - when the certificate is revoked without a reason code - "key_compromise" - when a private key is compromised - "ca_compromise" - when the CA issuing the certificate is compromised - "affiliation_changed" - when the certificate subject name changed - "superseded" - when the certificate was replaced with a new one - "cessation_of_operation" - when the certificate is no longer needed - "certificate_hold" - when the certificate is temporarily invalid - "remove_from_crl" - only delta CRLs - when temporary hold is removed - "privilege_withdrawn" - one of the usages for a certificate was removed - "unknown" - when the responder doesn't know about the certificate being requested """ if value is not None: if not isinstance(value, str_cls): raise TypeError(_pretty_message( ''' certificate_status must be a unicode string, not %s ''', _type_name(value) )) valid_certificate_statuses = set([ 'good', 'revoked', 'key_compromise', 'ca_compromise', 'affiliation_changed', 'superseded', 'cessation_of_operation', 'certificate_hold', 'remove_from_crl', 'privilege_withdrawn', 'unknown', ]) if value not in valid_certificate_statuses: raise ValueError(_pretty_message( ''' certificate_status must be one of "good", "revoked", "key_compromise", "ca_compromise", "affiliation_changed", "superseded", "cessation_of_operation", "certificate_hold", "remove_from_crl", "privilege_withdrawn", "unknown" not %s ''', repr(value) )) self._certificate_status = value @_writer def revocation_date(self, value): """ A datetime.datetime object of when the certificate was revoked, if the status is not "good" or "unknown". """ if value is not None and not isinstance(value, datetime): raise TypeError(_pretty_message( ''' revocation_date must be an instance of datetime.datetime, not %s ''', _type_name(value) )) self._revocation_date = value @_writer def certificate_issuer(self, value): """ An asn1crypto.x509.Certificate object of the issuer of the certificate. This should only be set if the OCSP responder is not the issuer of the certificate, but instead a special certificate only for OCSP responses. """ if value is not None: is_oscrypto = isinstance(value, asymmetric.Certificate) if not is_oscrypto and not isinstance(value, x509.Certificate): raise TypeError(_pretty_message( ''' certificate_issuer must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(value) )) if is_oscrypto: value = value.asn1 self._certificate_issuer = value @_writer def hash_algo(self, value): """ A unicode string of the hash algorithm to use when signing the request - "sha1", "sha256" (default) or "sha512". """ if value not in set(['sha1', 'sha256', 'sha512']): raise ValueError(_pretty_message( ''' hash_algo must be one of "sha1", "sha256", "sha512", not %s ''', repr(value) )) self._hash_algo = value @_writer def key_hash_algo(self, value): """ A unicode string of the hash algorithm to use when creating the certificate identifier - "sha1" (default), or "sha256". """ if value not in set(['sha1', 'sha256']): raise ValueError(_pretty_message( ''' hash_algo must be one of "sha1", "sha256", not %s ''', repr(value) )) self._key_hash_algo = value @_writer def nonce(self, value): """ The nonce that was provided during the request. """ if not isinstance(value, byte_cls): raise TypeError(_pretty_message( ''' nonce must be a byte string, not %s ''', _type_name(value) )) self._nonce = value @_writer def this_update(self, value): """ A datetime.datetime object of when the response was generated. """ if not isinstance(value, datetime): raise TypeError(_pretty_message( ''' this_update must be an instance of datetime.datetime, not %s ''', _type_name(value) )) self._this_update = value @_writer def next_update(self, value): """ A datetime.datetime object of when the response may next change. This should only be set if responses are cached. If responses are generated fresh on every request, this should not be set. """ if not isinstance(value, datetime): raise TypeError(_pretty_message( ''' next_update must be an instance of datetime.datetime, not %s ''', _type_name(value) )) self._next_update = value def set_extension(self, name, value): """ Sets the value for an extension using a fully constructed asn1crypto.core.Asn1Value object. Normally this should not be needed, and the convenience attributes should be sufficient. See the definition of asn1crypto.ocsp.SingleResponseExtension and asn1crypto.ocsp.ResponseDataExtension to determine the appropriate object type for a given extension. Extensions are marked as critical when RFC 6960 indicates so. :param name: A unicode string of an extension id name from asn1crypto.ocsp.SingleResponseExtensionId or asn1crypto.ocsp.ResponseDataExtensionId. If the extension is not one defined in those classes, this must be an instance of one of the classes instead of a unicode string. :param value: A value object per the specs defined by asn1crypto.ocsp.SingleResponseExtension or asn1crypto.ocsp.ResponseDataExtension """ if isinstance(name, str_cls): response_data_extension_oids = set([ 'nonce', 'extended_revoke', '1.3.6.1.5.5.7.48.1.2', '1.3.6.1.5.5.7.48.1.9' ]) single_response_extension_oids = set([ 'crl', 'archive_cutoff', 'crl_reason', 'invalidity_date', 'certificate_issuer', '1.3.6.1.5.5.7.48.1.3', '1.3.6.1.5.5.7.48.1.6', '2.5.29.21', '2.5.29.24', '2.5.29.29' ]) if name in response_data_extension_oids: name = ocsp.ResponseDataExtensionId(name) elif name in single_response_extension_oids: name = ocsp.SingleResponseExtensionId(name) else: raise ValueError(_pretty_message( ''' name must be a unicode string from asn1crypto.ocsp.ResponseDataExtensionId or asn1crypto.ocsp.SingleResponseExtensionId, not %s ''', repr(name) )) if isinstance(name, ocsp.ResponseDataExtensionId): extension = ocsp.ResponseDataExtension({'extn_id': name}) elif isinstance(name, ocsp.SingleResponseExtensionId): extension = ocsp.SingleResponseExtension({'extn_id': name}) else: raise TypeError(_pretty_message( ''' name must be a unicode string or an instance of asn1crypto.ocsp.SingleResponseExtensionId or asn1crypto.ocsp.ResponseDataExtensionId, not %s ''', _type_name(name) )) # We use native here to convert OIDs to meaningful names name = extension['extn_id'].native spec = extension.spec('extn_value') if name == 'nonce': raise ValueError(_pretty_message( ''' The nonce value should be set via the .nonce attribute, not the .set_extension() method ''' )) if name == 'crl_reason': raise ValueError(_pretty_message( ''' The crl_reason value should be set via the certificate_status parameter of the OCSPResponseBuilder() constructor, not the .set_extension() method ''' )) if name == 'certificate_issuer': raise ValueError(_pretty_message( ''' The certificate_issuer value should be set via the .certificate_issuer attribute, not the .set_extension() method ''' )) if not isinstance(value, spec) and value is not None: raise TypeError(_pretty_message( ''' value must be an instance of %s, not %s ''', _type_name(spec), _type_name(value) )) if isinstance(extension, ocsp.ResponseDataExtension): extn_dict = self._response_data_extensions else: extn_dict = self._single_response_extensions if value is None: if name in extn_dict: del extn_dict[name] else: extn_dict[name] = value def build(self, responder_private_key=None, responder_certificate=None): """ Validates the request information, constructs the ASN.1 structure and signs it. The responder_private_key and responder_certificate parameters are only required if the response_status is "successful". :param responder_private_key: An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey object for the private key to sign the response with :param responder_certificate: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate associated with the private key :return: An asn1crypto.ocsp.OCSPResponse object of the response """ if self._response_status != 'successful': return ocsp.OCSPResponse({ 'response_status': self._response_status }) is_oscrypto = isinstance(responder_private_key, asymmetric.PrivateKey) if not isinstance(responder_private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError(_pretty_message( ''' responder_private_key must be an instance of asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey, not %s ''', _type_name(responder_private_key) )) cert_is_oscrypto = isinstance(responder_certificate, asymmetric.Certificate) if not isinstance(responder_certificate, x509.Certificate) and not cert_is_oscrypto: raise TypeError(_pretty_message( ''' responder_certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(responder_certificate) )) if cert_is_oscrypto: responder_certificate = responder_certificate.asn1 if self._certificate is None: raise ValueError(_pretty_message( ''' certificate must be set if the response_status is "successful" ''' )) if self._certificate_status is None: raise ValueError(_pretty_message( ''' certificate_status must be set if the response_status is "successful" ''' )) def _make_extension(name, value): return { 'extn_id': name, 'critical': False, 'extn_value': value } response_data_extensions = [] single_response_extensions = [] for name, value in self._response_data_extensions.items(): response_data_extensions.append(_make_extension(name, value)) if self._nonce: response_data_extensions.append( _make_extension('nonce', self._nonce) ) if not response_data_extensions: response_data_extensions = None for name, value in self._single_response_extensions.items(): single_response_extensions.append(_make_extension(name, value)) if self._certificate_issuer: single_response_extensions.append( _make_extension( 'certificate_issuer', [ x509.GeneralName( name='directory_name', value=self._certificate_issuer.subject ) ] ) ) if not single_response_extensions: single_response_extensions = None responder_key_hash = getattr(responder_certificate.public_key, self._key_hash_algo) if self._certificate_status == 'good': cert_status = ocsp.CertStatus( name='good', value=core.Null() ) elif self._certificate_status == 'unknown': cert_status = ocsp.CertStatus( name='unknown', value=core.Null() ) else: status = self._certificate_status reason = status if status != 'revoked' else 'unspecified' cert_status = ocsp.CertStatus( name='revoked', value={ 'revocation_time': self._revocation_date, 'revocation_reason': reason, } ) issuer = self._certificate_issuer if self._certificate_issuer else responder_certificate if issuer.subject != self._certificate.issuer: raise ValueError(_pretty_message( ''' responder_certificate does not appear to be the issuer for the certificate. Perhaps set the .certificate_issuer attribute? ''' )) produced_at = datetime.now(timezone.utc) if self._this_update is None: self._this_update = produced_at if self._next_update is None: self._next_update = self._this_update + timedelta(days=7) response_data = ocsp.ResponseData({ 'responder_id': ocsp.ResponderId(name='by_key', value=responder_key_hash), 'produced_at': produced_at, 'responses': [ { 'cert_id': { 'hash_algorithm': { 'algorithm': self._key_hash_algo }, 'issuer_name_hash': getattr(self._certificate.issuer, self._key_hash_algo), 'issuer_key_hash': getattr(issuer.public_key, self._key_hash_algo), 'serial_number': self._certificate.serial_number, }, 'cert_status': cert_status, 'this_update': self._this_update, 'next_update': self._next_update, 'single_extensions': single_response_extensions } ], 'response_extensions': response_data_extensions }) signature_algo = responder_private_key.algorithm if signature_algo == 'ec': signature_algo = 'ecdsa' signature_algorithm_id = '%s_%s' % (self._hash_algo, signature_algo) if responder_private_key.algorithm == 'rsa': sign_func = asymmetric.rsa_pkcs1v15_sign elif responder_private_key.algorithm == 'dsa': sign_func = asymmetric.dsa_sign elif responder_private_key.algorithm == 'ec': sign_func = asymmetric.ecdsa_sign if not is_oscrypto: responder_private_key = asymmetric.load_private_key(responder_private_key) signature_bytes = sign_func(responder_private_key, response_data.dump(), self._hash_algo) certs = None if self._certificate_issuer: certs = [responder_certificate] return ocsp.OCSPResponse({ 'response_status': self._response_status, 'response_bytes': { 'response_type': 'basic_ocsp_response', 'response': { 'tbs_response_data': response_data, 'signature_algorithm': {'algorithm': signature_algorithm_id}, 'signature': signature_bytes, 'certs': certs } } }) def _pretty_message(string, *params): """ Takes a multi-line string and does the following: - dedents - converts newlines with text before and after into a single line - strips leading and trailing whitespace :param string: The string to format :param *params: Params to interpolate into the string :return: The formatted string """ output = textwrap.dedent(string) # Unwrap lines, taking into account bulleted lists, ordered lists and # underlines consisting of = signs if output.find('\n') != -1: output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output) if params: output = output % params output = output.strip() return output def _type_name(value): """ :param value: A value to get the object name of :return: A unicode string of the object name """ if inspect.isclass(value): cls = value else: cls = value.__class__ if cls.__module__ in set(['builtins', '__builtin__']): return cls.__name__ return '%s.%s' % (cls.__module__, cls.__name__) ocspbuilder-0.10.2/readme.md000066400000000000000000000045551274666462600157200ustar00rootroot00000000000000# ocspbuilder A Python library for creating and signing online certificate status protocol (OCSP) requests and responses for X.509 certificates. - [Related Crypto Libraries](#related-crypto-libraries) - [Current Release](#current-release) - [Dependencies](#dependencies) - [Installation](#installation) - [License](#license) - [Documentation](#documentation) - [Continuous Integration](#continuous-integration) - [Testing](#testing) - [Development](#development) ## Related Crypto Libraries *ocspbuilder* is part of the modularcrypto family of Python packages: - [asn1crypto](https://github.com/wbond/asn1crypto) - [oscrypto](https://github.com/wbond/oscrypto) - [csrbuilder](https://github.com/wbond/csrbuilder) - [certbuilder](https://github.com/wbond/certbuilder) - [crlbuilder](https://github.com/wbond/crlbuilder) - [ocspbuilder](https://github.com/wbond/ocspbuilder) - [certvalidator](https://github.com/wbond/certvalidator) ## Current Release 0.10.2 - [changelog](changelog.md) ## Dependencies - [*asn1crypto*](https://github.com/wbond/asn1crypto) - [*oscrypto*](https://github.com/wbond/oscrypto) - Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 or pypy ## Installation ```bash pip install ocspbuilder ``` ## License *ocspbuilder* is licensed under the terms of the MIT license. See the [LICENSE](LICENSE) file for the exact license text. ## Documentation [*ocspbuilder* documentation](docs/readme.md) ## Continuous Integration - [Windows](https://ci.appveyor.com/project/wbond/ocspbuilder/history) via AppVeyor - [OS X & Linux](https://travis-ci.org/wbond/ocspbuilder/builds) via Travis CI ## Testing Tests are written using `unittest` and require no third-party packages: ```bash python run.py tests ``` To run only some tests, pass a regular expression as a parameter to `tests`. ```bash python run.py tests build ``` ## Development To install required development dependencies, execute: ```bash pip install -r dev-requirements.txt ``` The following commands will run the linter and test coverage: ```bash python run.py lint python run.py coverage ``` The following will regenerate the API documentation: ```bash python run.py api_docs ``` After creating a [semver](http://semver.org/) git tag, a `.tar.gz` and `.whl` of the package can be created and uploaded to [PyPi](https://pypi.python.org/pypi/ocspbuilder) by executing: ```bash python run.py release ``` ocspbuilder-0.10.2/run.py000077500000000000000000000023101274666462600153050ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import sys if sys.version_info < (3,): byte_cls = str else: byte_cls = bytes def show_usage(): print('Usage: run.py (api_docs | lint | tests [regex] | coverage | ci | release)', file=sys.stderr) sys.exit(1) def get_arg(num): if len(sys.argv) < num + 1: return None arg = sys.argv[num] if isinstance(arg, byte_cls): arg = arg.decode('utf-8') return arg if len(sys.argv) < 2 or len(sys.argv) > 3: show_usage() task = get_arg(1) if task not in set(['api_docs', 'lint', 'tests', 'coverage', 'ci', 'release']): show_usage() if task != 'tests' and len(sys.argv) == 3: show_usage() params = [] if task == 'api_docs': from dev.api_docs import run elif task == 'lint': from dev.lint import run elif task == 'tests': from dev.tests import run matcher = get_arg(2) if matcher: params.append(matcher) elif task == 'coverage': from dev.coverage import run elif task == 'ci': from dev.ci import run elif task == 'release': from dev.release import run result = run(*params) sys.exit(int(not result)) ocspbuilder-0.10.2/scripts/000077500000000000000000000000001274666462600156175ustar00rootroot00000000000000ocspbuilder-0.10.2/scripts/create_pair.py000066400000000000000000000024471274666462600204560ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import os from oscrypto import asymmetric from certbuilder import CertificateBuilder fixtures_dir = os.path.join(os.path.dirname(__file__), '..', 'tests', 'fixtures') root_ca_private_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) root_ca_certificate = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) root_ocsp_public_key, root_ocsp_private_key = asymmetric.generate_pair('rsa', bit_size=2048) with open(os.path.join(fixtures_dir, 'test-ocsp.key'), 'wb') as f: f.write(asymmetric.dump_private_key(root_ocsp_private_key, 'password', target_ms=20)) builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'Codex Non Sufficit LC', 'organization_unit_name': 'Testing', 'common_name': 'CodexNS OCSP Responder', }, root_ocsp_public_key ) builder.extended_key_usage = set(['ocsp_signing']) builder.issuer = root_ca_certificate root_ocsp_certificate = builder.build(root_ca_private_key) with open(os.path.join(fixtures_dir, 'test-ocsp.crt'), 'wb') as f: f.write(asymmetric.dump_certificate(root_ocsp_certificate)) ocspbuilder-0.10.2/setup.py000066400000000000000000000036221274666462600156450ustar00rootroot00000000000000import os import shutil from setuptools import setup, find_packages, Command import ocspbuilder class CleanCommand(Command): user_options = [ ('all', None, '(Compatibility with original clean command)') ] def initialize_options(self): self.all = False def finalize_options(self): pass def run(self): folder = os.path.dirname(os.path.abspath(__file__)) for sub_folder in ['build', 'dist', 'ocspbuilder.egg-info']: full_path = os.path.join(folder, sub_folder) if os.path.exists(full_path): shutil.rmtree(full_path) setup( name='ocspbuilder', version=ocspbuilder.__version__, description=( 'Creates and signs online certificate status protocol (OCSP) ' 'requests and responses for X.509 certificates' ), long_description='Docs for this project are maintained at https://github.com/wbond/ocspbuilder#readme.', url='https://github.com/wbond/ocspbuilder', author='wbond', author_email='will@wbond.net', license='MIT', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Security :: Cryptography', ], keywords='crypto pki x509 certificate ocsp', install_requires=[ 'asn1crypto>=0.18.1', 'oscrypto>=0.16.1' ], packages=find_packages(exclude=['tests*', 'dev*']), test_suite='dev.tests.make_suite', cmdclass={ 'clean': CleanCommand, } ) ocspbuilder-0.10.2/tests/000077500000000000000000000000001274666462600152725ustar00rootroot00000000000000ocspbuilder-0.10.2/tests/__init__.py000066400000000000000000000000001274666462600173710ustar00rootroot00000000000000ocspbuilder-0.10.2/tests/_unittest_compat.py000066400000000000000000000053451274666462600212340ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import sys import unittest import re _non_local = {'patched': False} def patch(): if not sys.version_info < (2, 7): return if _non_local['patched']: return unittest.TestCase.assertGreaterEqual = _assert_greater_equal unittest.TestCase.assertLess = _assert_less unittest.TestCase.assertRaises = _assert_raises unittest.TestCase.assertRaisesRegexp = _assert_raises_regexp _non_local['patched'] = True def _assert_greater_equal(self, a, b, msg=None): if not a >= b: standard_msg = '%s not greater than or equal to %s' % (unittest.util.safe_repr(a), unittest.util.safe_repr(b)) self.fail(self._formatMessage(msg, standard_msg)) def _assert_less(self, a, b, msg=None): if not a < b: standard_msg = '%s not less than %s' % (unittest.util.safe_repr(a), unittest.util.safe_repr(b)) self.fail(self._formatMessage(msg, standard_msg)) def _assert_raises(self, excClass, callableObj=None, *args, **kwargs): # noqa context = _AssertRaisesContext(excClass, self) if callableObj is None: return context with context: callableObj(*args, **kwargs) def _assert_raises_regexp(self, expected_exception, expected_regexp, callable_obj=None, *args, **kwargs): if expected_regexp is not None: expected_regexp = re.compile(expected_regexp) context = _AssertRaisesContext(expected_exception, self, expected_regexp) if callable_obj is None: return context with context: callable_obj(*args, **kwargs) class _AssertRaisesContext(object): def __init__(self, expected, test_case, expected_regexp=None): self.expected = expected self.failureException = test_case.failureException self.expected_regexp = expected_regexp def __enter__(self): return self def __exit__(self, exc_type, exc_value, tb): if exc_type is None: try: exc_name = self.expected.__name__ except AttributeError: exc_name = str(self.expected) raise self.failureException( "{0} not raised".format(exc_name)) if not issubclass(exc_type, self.expected): # let unexpected exceptions pass through return False self.exception = exc_value # store for later retrieval if self.expected_regexp is None: return True expected_regexp = self.expected_regexp if not expected_regexp.search(str(exc_value)): raise self.failureException( '"%s" does not match "%s"' % (expected_regexp.pattern, str(exc_value)) ) return True ocspbuilder-0.10.2/tests/fixtures/000077500000000000000000000000001274666462600171435ustar00rootroot00000000000000ocspbuilder-0.10.2/tests/fixtures/test-inter.crt000066400000000000000000000026541274666462600217620ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEBDCCAuygAwIBAgIDGEN5MA0GCSqGSIb3DQEBCwUAMIGdMQswCQYDVQQGEwJV UzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmV3YnVyeTEeMBwG A1UEChMVQ29kZXggTm9uIFN1ZmZpY2l0IExDMRAwDgYDVQQLEwdUZXN0aW5nMRIw EAYDVQQDEwlXaWxsIEJvbmQxHjAcBgkqhkiG9w0BCQEWD3dpbGxAY29kZXhucy5p bzAeFw0xNTA1MDkwMDQxMzlaFw0yNTA1MDYwMDQxMzlaMIGYMQswCQYDVQQGEwJV UzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEeMBwGA1UEChMVQ29kZXggTm9uIFN1 ZmZpY2l0IExDMR0wGwYDVQQLExRUZXN0aW5nIEludGVybWVkaWF0ZTESMBAGA1UE AxMJV2lsbCBCb25kMR4wHAYJKoZIhvcNAQkBFg93aWxsQGNvZGV4bnMuaW8wggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/PVOdogtqe2gqXZ20dWB5NFxj JX6z6A57zGqT11OfiNmckIqxMRK8eToR69/DJmVNbQTJy/cFexZ33db+UrtEItxO klFFbG/6TjmqsOYF87TkJqGX7zXs/Z7D1Xl0/2CNh6vPPPbi/o1FHtKnk4HYOnyU LRMxxtt06O/u//wdnWXiSAOKOasLT7eP6Fuok28+BeQ29GB3UNL8oC7biyhv89yN VhDlHAkk+zfrGP4dNqxOY8SkwI0GYki9skEckwf7Nyz+vA8zVJaIymte/eRicCFF YFxh+QtQhyxF7DwsB5/XIgEWKRXnd/ivyORMlIVc83CHCM4ryvXgG0+I7WuZAgMB AAGjUDBOMB0GA1UdDgQWBBTSCv0uJdG3IddQfrukfb8071JeAjAfBgNVHSMEGDAW gBS+QoU9zP/j+SgCj35YVrT9A1zqSzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB CwUAA4IBAQAKnbphxDozjob2qobtO5QRfZNwIANr/Pyz6SoD8UnJDfZAg9y1jb/I OutcURjYfV4vyLKI5oyhepyBMkuwvnB/lRpLERAwv5lJgq4sIGDn5Sp56yYcxazC QXLyG5YJ2Q29kVuq9//IE/dkLVaP444iJZZ7IgrGN4EFQwvFRVa6toeVpwzp8kkn 5eQaiKQYt/1dVsQ5mv8ksT/gNJRMD4xJ7xvlats+Jc6cyygJk18h+JqMYDoFTTu6 fJmjtnjPFg+XU0q9GnJfrW+FjxYGMv+5tv0pJrujEq7+8gz5A4viSCzHHoc4O/Qs 0Jt6dQ/xmGYswxjrR/ZgsSjPx3lib7XC -----END CERTIFICATE----- ocspbuilder-0.10.2/tests/fixtures/test-ocsp.crt000066400000000000000000000026501274666462600216010ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIID/zCCAumgAwIBAgIIVkZXquZ41NIwCwYJKoZIhvcNAQELMIGdMQswCQYDVQQG EwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmV3YnVyeTEe MBwGA1UEChMVQ29kZXggTm9uIFN1ZmZpY2l0IExDMRAwDgYDVQQLEwdUZXN0aW5n MRIwEAYDVQQDEwlXaWxsIEJvbmQxHjAcBgkqhkiG9w0BCQEWD3dpbGxAY29kZXhu cy5pbzAeFw0xNTExMTMyMTM1MzhaFw0xNjExMTIyMTM1MzhaMHAxbjAJBgNVBAYT AlVTMBQGA1UECAwNTWFzc2FjaHVzZXR0czAOBgNVBAcMB05ld2J1cnkwHAYDVQQK DBVDb2RleCBOb24gU3VmZmljaXQgTEMwHQYDVQQDDBZDb2RleE5TIE9DU1AgUmVz cG9uZGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4QoiJo5OMex+ OcEXqGgOQBD62SGNFAAPuSLwxVflGV9gLzoKg1ipyKgYfrrct4soFJqEiayfYEB5 psB48pwq8Kz6GtbE7p/MQ4g9srhDEkDThz3LAjKalHF+OfM+aF9mLVK6ZBRi5ICb 2q5UDZbYFsQDhPE97hRditQRA5MKfoPx5h0OaRhV15cmgURVQUsN1mNHWETLYcJr /Pe+SvsPnCEsmttPuqaQPibmJAisMhhLSHRrMrEO1k6wsbSF/y67csLrdUbfmVHW rhCD+KW5cFEOpidHQ+6ese09FKqZ4vvRvTWbfbN9vMCKV/H33KnpJBXLtNoXvoPf HwdAmOg0SQIDAQABo3MwcTAfBgNVHSMEGDAWgBS+QoU9zP/j+SgCj35YVrT9A1zq SzAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMJMB0GA1UdDgQWBBQ17XC8 K8tzRB3nx8yXm69gM1MdtjAPBgNVHQ8BAf8EBQMDB6AAMAsGCSqGSIb3DQEBCwOC AQEALkYqxg2+FXxGc0FU/1+zWYDjX+Tt4zlffGV8UtLqSUibMLdrv7nQ5P296nZc EGC92yhWUWTwGBvRpErh7vG00eZJH7j36uC+NQfFKsdvyO51bDc9nW/s+dCZbX7E jTH4wvj3qYsIg93SW/qURzTg32mu+grOCTG0Ad07tFCuZMYucx9Bma43QVz5tX5S wBT9f1q52XXMPGHB1xYWAfq3KWo3hxZJJwcjS5YXF8bUAo8p16nl8255w5fpOP7Y oVghHQuUsempbnmbLLr5HXMdAFfwHJRXc7YBm0FOtnt31LQvYObKgr9bpsFzVctU FNK5LK4oPeFyM05IQOKD0rlgsg== -----END CERTIFICATE----- ocspbuilder-0.10.2/tests/fixtures/test-ocsp.key000066400000000000000000000035631274666462600216050ustar00rootroot00000000000000-----BEGIN ENCRYPTED PRIVATE KEY----- MIIFRTBvBgkqhkiG9w0BBQ0wYjBBBgkqhkiG9w0BBQwwNAQgcKLCTVXKCNTTnY8u AxeyuaZBZGUwj7mh8itQcfh8I8oCAk4gMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUD BAEqBBBgFfPRMB/jhHUI2AXw5UTZBIIE0Bz5QqEnvEip8KtBfWmVgLM08102UNgx wnfEjbfdjsT7ziWrDHE/0liAN7YZIPnHlCUDA4i2tnuud5Q2fMJewyfLOjz8BdnQ yMp1C1xoXhsaQMTk1v1qTKSdxAUAvqDfJOwM/mCcZUnKJEPBzV2MVBQOfktqIDsG 2TDDo1v3jLk13Il7V+J6IM8kFoAdbM8cdisVRDTymA2T+jPpMuHZNMS31dWO0P40 rl31+g7zuT2n7KE36r+Mkr+w92CNtOhLiJQqZZGlCqGW0Q+ExdBNjkZCL32uHhJx fDVeMajRh2DTfdrF/jbNgeC1qrxXUiUKXfdosCtfUBf4OmfVjY2HuzFNPQZCSiJ3 yTPW1y03gotXC5gbxgF9exFPINtLFA09qpbD9bmwmH5AxUNwA99IwHWNPhhHBbGN mjI9eBMGXCRoDxxoo9w/DoOVxgX6uBtXKLEfLZZEduOXj/JTZRVSKy6uwEw27W24 SjVWk7G9HTzp31DEZ9ofiY0HpF2g4iivBiVJrf7HAwkXWoUPX1vTefMIeVgxl9Xa /U5ijb8yn0CfJ9GgWgNT953koCDgyIal1Mv9ByNSyyRSp3c45P20kitnj+6HBwES GwZqBa83P0jpx0OWg/xiV9lSOjbfdNrXFWDOuhOHtexXX4neIBeOLfHMIJYNXgCE rxoSvPr+TSSk399hS1t8g41728us0btfdHKoe5daHO4s/eaFWKs/8bCQ212uyW9Z MkjtJvviDbTzfacnRP9S4EPVQ4yl9z2jlLIh1p+qnt7txDDwmNHHt9ra3Th1/lq8 YCd13qPoNWA9Z/NOTWRxYP6gtKA2sDbOkFwGF2tYMBmPQJkOPjOdw5fFBsYqkBb+ iAvf78pUs7srBt4bKqxfXU1auRVCHrMXNf+n6JDKK63Xu7KaKtPJYa+YglwjR7lP pJRDgq1SPEruUwfM1oTZJ426An1Zg/i34/eq+FJxSpx2M6BLO513jtC76TIzn6Ev ao7gBSNELEXuQlVFRJSxssKUjiHvIK95nFotMuglDDYMOzMIljvGl96xDXvt8E+4 X0nKMMQTd8aAdpnHXI+ho/mdPA/Du1oS4NdxSJNOu+TaM9/326YnvLfxKuZDDyGT uRDDcNiaEgXauhC8SOZK+ssZcKfbUgjerQ+O/Mc9VlCgmW5XQKu/isNkABZoPCJ0 S1HQSugS+my2GpJp/yYJWaUK2Ev4usUTsY4yGasfCjbqdn3hX28A9cdetQt5bTGa Nsb+YvrqlFMcYIUB+7CYIAOZCMBzBsQ6nfdVSTGTmpNHlLfLMqtx0zd2BcDTqi9X bR61HgIVXKwh3ao7nCbA0r3GtFVs/3lKpwNp2Is5HVJgsGfIGszs3/4iCaehY33C k9r0oQO7ds8I+JeqGFkhbgIyeu+g0BjVHGQleh8biNTXK1NvnlQ3iNX6b8TqyeOJ y1wIPG7HJVPuQA4/pjtJOmZg6QqUmlrrYPjLYGYYA4TuVgY90fWl9jgj44ujf8HG sBLgKjjMnUCSBh1G4FOh9e+O8jv+jvU+IKo4haZaN+XKgcrd9fK7lJuUnFSRd8vb EKSZ9LNXz3hmNp9GtFipTi7ax3UMaiMQCFi+XRbYIOyosvU0ekks62aO9cqZoqdj PEmXCD3dKjur -----END ENCRYPTED PRIVATE KEY----- ocspbuilder-0.10.2/tests/fixtures/test-third.crt000066400000000000000000000026401274666462600217460ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIID+zCCAuOgAwIBAgIFAJOEAykwDQYJKoZIhvcNAQELBQAwgZgxCzAJBgNVBAYT AlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMR4wHAYDVQQKExVDb2RleCBOb24g U3VmZmljaXQgTEMxHTAbBgNVBAsTFFRlc3RpbmcgSW50ZXJtZWRpYXRlMRIwEAYD VQQDEwlXaWxsIEJvbmQxHjAcBgkqhkiG9w0BCQEWD3dpbGxAY29kZXhucy5pbzAe Fw0xNTA1MTEyMTQyNTZaFw0yNTA1MDgyMTQyNTZaMIGgMQswCQYDVQQGEwJVUzEW MBQGA1UECBMNTWFzc2FjaHVzZXR0czEeMBwGA1UEChMVQ29kZXggTm9uIFN1ZmZp Y2l0IExDMSUwIwYDVQQLExxUZXN0IFRoaXJkLUxldmVsIENlcnRpZmljYXRlMRIw EAYDVQQDEwlXaWxsIEJvbmQxHjAcBgkqhkiG9w0BCQEWD3dpbGxAY29kZXhucy5p bzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMAKLNVdAlEUbBeAKfSz 3Ymd78tt6l2jjEdz4QMqFkYDSbl5pMB0RP+yRKHVSr+gL99F3wAXHoMb81bFVItL dyx3vK1qK2FhMvmuw8yNUy3o4aIwE/Neobp4eWLWNWwdqmPyNpvc8g8HdVsnk97u 9ZUGoiHEblvLZ8uijvbxK5hbCkKHjOhHCDT5egtPKOyFqOWJ1WzINICRfEeIjZM0 cuLUWVJmzb9QJ8CmN4O6R1rYK131eIZ6FWlJTpx1n7MvygTOmDiyVE0NMfJFTzdj a8kQxiwdyqN9I9OfHqRefh8TCQBuAguuwPUyAu9Ve45eDqzfeWrgoyinZ0KXiqrH 5DUCAwEAAaNCMEAwHQYDVR0OBBYEFEQ44OAmhb+Yhtwb4R31MjC+q6wNMB8GA1Ud IwQYMBaAFNIK/S4l0bch11B+u6R9vzTvUl4CMA0GCSqGSIb3DQEBCwUAA4IBAQAb Wq6ueTRDgY5hDAcVn3j5Eaco88rzhhzgfnH4GSES/QOQlOEFbj0/zzAk7LCgcXq3 7ud/cbA0tuoFmra9Q4D3YEcL0xHiDlXkvzcy2MJ6MysRiQftvHE9o1f8lxWqEfHK EtL3espMfVE8zisiA7kS07Jm4t7OSBte21JVwqC+dlqYca2T0coJ47Fw/yVvlaQU O5h6wvHuUS4L0myNdW+/V2yoUex8C9OK3qs8H61ULcgoVnvn/DJJerad53LBfJR3 jmmamq6Pu7COWU07N7MOGkrG8bwal64ncdzwvTtBj9NdoDataw9LvjyAGxYDSY6X KiqBZoOHm108hjAfW+XC -----END CERTIFICATE----- ocspbuilder-0.10.2/tests/fixtures/test-third.key000066400000000000000000000032171274666462600217470ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAwAos1V0CURRsF4Ap9LPdiZ3vy23qXaOMR3PhAyoWRgNJuXmk wHRE/7JEodVKv6Av30XfABcegxvzVsVUi0t3LHe8rWorYWEy+a7DzI1TLejhojAT 816hunh5YtY1bB2qY/I2m9zyDwd1WyeT3u71lQaiIcRuW8tny6KO9vErmFsKQoeM 6EcINPl6C08o7IWo5YnVbMg0gJF8R4iNkzRy4tRZUmbNv1AnwKY3g7pHWtgrXfV4 hnoVaUlOnHWfsy/KBM6YOLJUTQ0x8kVPN2NryRDGLB3Ko30j058epF5+HxMJAG4C C67A9TIC71V7jl4OrN95auCjKKdnQpeKqsfkNQIDAQABAoIBAQCyxkYqcqV/eXWP Ax8L0I3CWScsyCxP87rZocStP3bwworVgaqgBx1ctEY0Ke2mKqemQNNysBMVluWX t6gW7LAK04TwI1AzHVtpGQrp1/7BVHUImZ1ZCJWilBjcq/Gbrpo65Pd1beBhoV3c +CEufmJc04oHyWe7SMZdyf0xYh5le2yq6qbRjihjOdGtGxJF/BkGT7MVGZwOE21Z wTtW9hto26rCSFoUDhnHNQLEBNSsxwIov2VJ1lp7VN6ruvisN3SddJl9068dvorn cLyYcelR7i9efM1/VdsI9xmQOaLF9p8HizYex/euQ7vyMp3APAepf5pCT4DkpZpD PmlJoUCBAoGBAPLwQpliNUofOIfmqiQ3/v0c5tTapoG9NwFyRtRfnm46Ou810OPs e/L89Wreo2+sfW54Y7J2ftqI6+XmuGRGCv0AgeZaYC8Hx7+o3uUitNTcwvVFqdKM SmDAhzy/WZGhHE5dDEAmXEDF0NNSgHWjNVwrewT6+RHdpv4EMavum5oRAoGBAMpd W62XfLVCVNxUCjRCMwozf46Cybx3NLwVtJKbB2kGDqUXYLs4f4F0OzFt5XPIluFA kQSro5CrexVXMhcIigOadk6dGegJPGFaAmw5ZSjyGLjr1wmaskCzTBqguDUta1Up IA6swmYK9eR8tHZNNFRBkL/KNEY9+ZwWdQ62WuPlAoGBAN0nMrmG2ZQcT84HgaNv BkVM5iWm1iUNJuG+MhRq50LY54WTrBGQ2lUdShx7iLTEhXrnRXrUvC4crwKewgUm biJbL+WPKDgoEQK8rAxTR+LvBNtbC3mMFLl3CqWWW+diju4XbmuHgDvG2I9Hb4Gn jY/WVSr3fX1yFe7vyngFwsjBAoGAZkO9k8EtTXBi8CEsMvKNVodl27/ucOaQ6MfT RA9CNGnSNs3UnWhUzzfMvhL6VIO288gsQP74HqD6B3PUJV20WVPSm7G6qM8aC1xw Qv7SR1no8nKEbh8WG6pAOGimDoGQby3kPGZDq0u4ranzjKFBY57qpnFp72FcZevX ZgLzdZ0CgYEAzhkOEjC82l1tm9oWbkrdLdx/Sox2mBEnxkkvt30oRUTt89xA2epS q/4zBCTSCg+OhHfASg+ek8zMOtKnOQnis8F2Z6lvboA1Hg/iWv6mZ7EYj78C5yZh nsdKM+U/5sxUa4RCc0Aj5s8irpZZfc0SxwmYZH/NMQuY3e3HMuyGLKM= -----END RSA PRIVATE KEY----- ocspbuilder-0.10.2/tests/fixtures/test.crt000066400000000000000000000032641274666462600206410ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIExzCCA6+gAwIBAgIJAL3l2aQQMVyCMA0GCSqGSIb3DQEBCwUAMIGdMQswCQYD VQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmV3YnVy eTEeMBwGA1UEChMVQ29kZXggTm9uIFN1ZmZpY2l0IExDMRAwDgYDVQQLEwdUZXN0 aW5nMRIwEAYDVQQDEwlXaWxsIEJvbmQxHjAcBgkqhkiG9w0BCQEWD3dpbGxAY29k ZXhucy5pbzAeFw0xNTA1MDYxNDM3MTZaFw0yNTA1MDMxNDM3MTZaMIGdMQswCQYD VQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEQMA4GA1UEBxMHTmV3YnVy eTEeMBwGA1UEChMVQ29kZXggTm9uIFN1ZmZpY2l0IExDMRAwDgYDVQQLEwdUZXN0 aW5nMRIwEAYDVQQDEwlXaWxsIEJvbmQxHjAcBgkqhkiG9w0BCQEWD3dpbGxAY29k ZXhucy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL1bKAd04uZK LAIqvVDeeBeq7FA2fpS5xkWcqHbarzvD1//EG/kCQirJr302nusjJFxdji3aVDRG Px0+WWwGajy+k2vYm0t7mSP/bmVGCM06ofvDZUMWV1Ld4SyInHruS1Qj4xHlB7/Z +mAWYpCudmAFIJEgtlHDzezqu6kLEVNB1lbLH+lPNyunwXC9FSYWhekjAyBaflFB koQV90jXfuTG7Ph0m4DAfZn5n5r/YpvmKEDkPkaW1mAt8qel4b8RklAh8t8vTSfv QuTeyw3GFcKe7KymKHIaDDxwwnALfGWNa3t7YoVZP9fVrghkR73DBCnHIx22uDHU TkwBmIdUL18CAwEAAaOCAQYwggECMB0GA1UdDgQWBBS+QoU9zP/j+SgCj35YVrT9 A1zqSzCB0gYDVR0jBIHKMIHHgBS+QoU9zP/j+SgCj35YVrT9A1zqS6GBo6SBoDCB nTELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEDAOBgNVBAcT B05ld2J1cnkxHjAcBgNVBAoTFUNvZGV4IE5vbiBTdWZmaWNpdCBMQzEQMA4GA1UE CxMHVGVzdGluZzESMBAGA1UEAxMJV2lsbCBCb25kMR4wHAYJKoZIhvcNAQkBFg93 aWxsQGNvZGV4bnMuaW+CCQC95dmkEDFcgjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 DQEBCwUAA4IBAQA/5fmvWSMRWqD6JalrNr3Saio5+c/LzxbQ7kZZX1EH5Bo+vhD6 Pfs6lrhzE5OVS86sOItPTtT2KQIE0wTxNfX9wQzjekH4Q2sZRpWaKvK6cH+YzqUK n3DiICpu8vau4wHDrlfV/C2XhKf5u5qIB+SyOhGDo+XhY0cjgh/FS2//1/qGxrfx TeOHxoRN5YH4yKNGNapL3XF+utpwVFddmSRUWCBr7Fof9RJlzPCcVP/svTAXDi/y dhHevPEr21oYqX0KnJJa0JvJx92K8YKFTVj2x5PPCn9qze5C/Re/JFbCIuwq7kcX 3WQRd+S9zHbtG/4g3DnGibdczSw9529fZZw3 -----END CERTIFICATE----- ocspbuilder-0.10.2/tests/fixtures/test.key000066400000000000000000000032171274666462600206370ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAvVsoB3Ti5kosAiq9UN54F6rsUDZ+lLnGRZyodtqvO8PX/8Qb +QJCKsmvfTae6yMkXF2OLdpUNEY/HT5ZbAZqPL6Ta9ibS3uZI/9uZUYIzTqh+8Nl QxZXUt3hLIiceu5LVCPjEeUHv9n6YBZikK52YAUgkSC2UcPN7Oq7qQsRU0HWVssf 6U83K6fBcL0VJhaF6SMDIFp+UUGShBX3SNd+5Mbs+HSbgMB9mfmfmv9im+YoQOQ+ RpbWYC3yp6XhvxGSUCHy3y9NJ+9C5N7LDcYVwp7srKYochoMPHDCcAt8ZY1re3ti hVk/19WuCGRHvcMEKccjHba4MdROTAGYh1QvXwIDAQABAoIBAE8NH0j9ozxA+t5s uVxpg/ldggp6tZ2hcQTewfXclgt9V0+Pr53lM3ppeLntc6r2oNdut0ytOToZmX+7 59kRVIjHhwQfCbYZg3VjzdK5yjLjp3xTtpKrYQlXWAoffjRUB165HLL7yqBtf/ld XwjHzOOJQG9WGMdJ105xMKcB19nJijgY19Eg/9svNQq6wwfDACKAYXusoz8ApGoz T6b8+o2nINQHaCgmf5qPbg44JfX3+k2er7bK+mk7cuoOIWOB5ItSfyq4GdbLM+XW /GIGkclXUOWRDmitudoLKtv5WtvYrsWe1iznHIrlIy7gI1i9Z+V9mEYb+aZPDtjB 4Kuy/gECgYEA7VBSov4Oph78yiIIwmJ6T+4+tdbJQ2lxV7GusSVjj76vmZASNJ7E gpR5woH1APEyHgnUkoRK8fGUHp+Lxms2rrYivcjXZ4DTvpZ/qQVJBNJ4pVyWn+iq 7gLro2wotiLDBzeG9oo46Czn0QCOk7tofxSbcEkhkO+Uuwc3ZSlEOfcCgYEAzEQf HQNFzBHqlvPVbfy9+IxnG+u33WEKSMr+1ek/hae43+D4vndBX1DPuhaWUapsU9Gg A5YIXOhCIrnx4MbMvcBn55CWIf7J+dEdPaUOGN+YtYbV29U45nX0dS4QXxBOVZwn qUBRDwptSFVaLHDUrOC4vnkST6iRHh/OJ5AuG9kCgYEAm8+SAiwWSCGuTbSc1au8 rMA68j7sc9NGNJKXpP1sahODzapXGa9oTGfZrciPqSezhR9lLzGm10WKv7R3HDaG d51kIAE+1Fk0LT044itzLrRVvBSXXLRxjcXjGrBH5pXaQOHHPhWwmVfqeEIKWprA WDeaetW5MSTsHQP27fdzMS8CgYA4DXl8PKmqlkAJrF+lDvYSfnTM9KI/3aE02H+V s6v6wUu6I8IeghsuTL60Ef6t6lZPqfZ/BWzGEfYUEXKOe/8zEtlwcfzA12oVY4zi naiAqtr89UM6UAiNNVEf1sQnUhIs6+z2RO/5cKMMdl+IUm4KAqCvpAmiUl+AJLot oSMGAQKBgQCcWwsTbMnFJrBxHVe05C8Y/DeU1Cj7TlKKuYSidASus0KhJv7bkSIE N3x3RJFDVrkEW30AH/b+oMKHSjileWP7NLCT4Y/d/ZybhavhOziol408GsZNe6YV Tzk/USt/a4ZBK1K5RlHmTiFYNXvecXqzHnk77OzCMsJtteW/gr1ABQ== -----END RSA PRIVATE KEY----- ocspbuilder-0.10.2/tests/test_ocsp_request_builder.py000066400000000000000000000075701274666462600231360ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function import unittest import os import asn1crypto.x509 from oscrypto import asymmetric from ocspbuilder import OCSPRequestBuilder from ._unittest_compat import patch patch() tests_root = os.path.dirname(__file__) fixtures_dir = os.path.join(tests_root, 'fixtures') class OCSPRequestBuilderTests(unittest.TestCase): def test_build_basic_request(self): issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) builder = OCSPRequestBuilder(subject_cert, issuer_cert) ocsp_request = builder.build() der_bytes = ocsp_request.dump() new_request = asn1crypto.ocsp.OCSPRequest.load(der_bytes) tbs_request = new_request['tbs_request'] self.assertEqual(None, new_request['optional_signature'].native) self.assertEqual('v1', tbs_request['version'].native) self.assertEqual(None, tbs_request['requestor_name'].native) self.assertEqual(1, len(tbs_request['request_list'])) request = tbs_request['request_list'][0] self.assertEqual('sha1', request['req_cert']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, request['req_cert']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, request['req_cert']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, request['req_cert']['serial_number'].native) self.assertEqual(0, len(request['single_request_extensions'])) self.assertEqual(1, len(tbs_request['request_extensions'])) extn = tbs_request['request_extensions'][0] self.assertEqual('nonce', extn['extn_id'].native) self.assertEqual(16, len(extn['extn_value'].parsed.native)) def test_build_signed_request(self): issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) requestor_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-third.crt')) requestor_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test-third.key')) builder = OCSPRequestBuilder(subject_cert, issuer_cert) ocsp_request = builder.build(requestor_key, requestor_cert, [subject_cert, issuer_cert]) der_bytes = ocsp_request.dump() new_request = asn1crypto.ocsp.OCSPRequest.load(der_bytes) tbs_request = new_request['tbs_request'] signature = new_request['optional_signature'] self.assertEqual('sha256', signature['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', signature['signature_algorithm'].signature_algo) self.assertEqual(3, len(signature['certs'])) self.assertEqual('v1', tbs_request['version'].native) self.assertEqual(requestor_cert.asn1.subject, tbs_request['requestor_name'].chosen) self.assertEqual(1, len(tbs_request['request_list'])) request = tbs_request['request_list'][0] self.assertEqual('sha1', request['req_cert']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, request['req_cert']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, request['req_cert']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, request['req_cert']['serial_number'].native) self.assertEqual(0, len(request['single_request_extensions'])) self.assertEqual(1, len(tbs_request['request_extensions'])) extn = tbs_request['request_extensions'][0] self.assertEqual('nonce', extn['extn_id'].native) self.assertEqual(16, len(extn['extn_value'].parsed.native)) ocspbuilder-0.10.2/tests/test_ocsp_response_builder.py000066400000000000000000000304441274666462600233000ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function from datetime import datetime import unittest import os import asn1crypto.x509 from oscrypto import asymmetric from asn1crypto.util import timezone from ocspbuilder import OCSPResponseBuilder from ._unittest_compat import patch patch() tests_root = os.path.dirname(__file__) fixtures_dir = os.path.join(tests_root, 'fixtures') class OCSPResponseBuilderTests(unittest.TestCase): def test_build_good_response(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) builder = OCSPResponseBuilder('successful', subject_cert, 'good') ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual( issuer_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native ) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual('sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('good', cert_response['cert_status'].name) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions) def test_build_no_certificate(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate = None ocsp_response = builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate_status = None ocsp_response = builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert) ocsp_response = builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', None, 'good') ocsp_response = builder.build(issuer_key, issuer_cert) def test_build_revoked_response(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) revoked_time = datetime(2015, 9, 1, 12, 0, 0, tzinfo=timezone.utc) builder = OCSPResponseBuilder('successful', subject_cert, 'key_compromise', revoked_time) ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual( issuer_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native ) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual('sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('revoked', cert_response['cert_status'].name) self.assertEqual(revoked_time, cert_response['cert_status'].chosen['revocation_time'].native) self.assertEqual('key_compromise', cert_response['cert_status'].chosen['revocation_reason'].native) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions) def test_build_revoked_no_reason(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) revoked_time = datetime(2015, 9, 1, 12, 0, 0, tzinfo=timezone.utc) builder = OCSPResponseBuilder('successful', subject_cert, 'revoked', revoked_time) ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] cert_response = response_data['responses'][0] self.assertEqual('revoked', cert_response['cert_status'].name) self.assertEqual(revoked_time, cert_response['cert_status'].chosen['revocation_time'].native) self.assertEqual('unspecified', cert_response['cert_status'].chosen['revocation_reason'].native) def test_build_delegated_good_response(self): responder_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test-ocsp.key'), 'password') responder_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-ocsp.crt')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate_issuer = issuer_cert ocsp_response = builder.build(responder_key, responder_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual( responder_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native ) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual('sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('good', cert_response['cert_status'].name) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions) def test_build_unknown_response(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) builder = OCSPResponseBuilder('successful', subject_cert, 'unknown') ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual( issuer_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native ) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual('sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('unknown', cert_response['cert_status'].name) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions) def test_build_error_response(self): """ Build a response with error status. """ error_statuses = ['malformed_request', 'internal_error', 'try_later', 'sign_required', 'unauthorized'] for status in error_statuses: builder = OCSPResponseBuilder(status) ocsp_response = builder.build() der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) assert dict(new_response.native) == { 'response_status': status, 'response_bytes': None, } ocspbuilder-0.10.2/tox.ini000066400000000000000000000003261274666462600154440ustar00rootroot00000000000000[tox] envlist = py26,py27,py32,py33,py34,py35,pypy [testenv] deps = oscrypto asn1crypto flake8 commands = {envpython} run.py ci [pep8] max-line-length = 120 [flake8] max-line-length = 120 jobs = 1