Changeset - de1cc2505a56
[Not reviewed]
0 9 0
Branko Majic (branko) - 4 years ago 2020-06-07 22:17:44
branko@majic.rs
GC-37: Refactor key specification handling:

- Perform the key specification parsing within CLI module itself,
don't do it via crypto module.
- Pass-in tuple consisting out of algorithm and associated parameters
into the init command instead of key generator.
- Updated all tests to accomodate the change in init function
signature.
- Simplify the KeyGenerator class.
- Do not test if KeyGenerator class sets the properties via
constructor - it is sufficient to test string represenation and key
generation.
9 files changed with 143 insertions and 143 deletions:
0 comments (0 inline, 0 general)
gimmecert/cli.py
Show inline comments
 
@@ -22,13 +22,12 @@
 
import argparse
 
import os
 
import sys
 

	
 
from .decorators import subcommand_parser, get_subcommand_parser_setup_functions
 
from .commands import client, help_, init, renew, server, status, usage, ExitCode
 
from .crypto import KeyGenerator
 

	
 

	
 
ERROR_ARGUMENTS = 2
 
ERROR_GENERIC = 10
 

	
 

	
 
@@ -75,18 +74,46 @@ Examples:
 

	
 
    # Show information about CA hierarchy and issued certificates.
 
    gimmecert status
 
"""
 

	
 

	
 
def key_specification(specification):
 
    """
 
    Verifies and parses the passed-in key specification. This is a
 
    small utility function for use with the Python argument parser.
 

	
 
    :param specification: Key specification. Currently supported formats are: "rsa:KEY_SIZE".
 
    :type specification: str
 

	
 
    :returns: Parsed key algorithm and parameter(s) for the algorithm. For RSA, parameter is the RSA key size.
 
    :rtype: tuple(str, int)
 

	
 
    :raises ValueError: If passed-in specification is invalid.
 
    """
 

	
 
    try:
 
        algorithm, parameters = specification.split(":", 2)
 

	
 
        if algorithm == "rsa":
 
            parameters = int(parameters)
 
        else:
 
            raise ValueError()
 

	
 
    except ValueError:
 
        raise ValueError("Invalid key specification: '%s'" % specification)
 

	
 
    return algorithm, parameters
 

	
 

	
 
@subcommand_parser
 
def setup_init_subcommand_parser(parser, subparsers):
 
    subparser = subparsers.add_parser('init', description='Initialise CA hierarchy.')
 
    subparser.add_argument('--ca-base-name', '-b', help="Base name to use for CA naming. Default is to use the working directory base name.")
 
    subparser.add_argument('--ca-hierarchy-depth', '-d', type=int, help="Depth of CA hierarchy to generate. Default is 1", default=1)
 
    subparser.add_argument('--key-specification', '-k', type=KeyGenerator,
 
    subparser.add_argument('--key-specification', '-k', type=key_specification,
 
                           help='''Default specification/parameters to use for private key generation. \
 
    For RSA keys, use format rsa:BIT_LENGTH. Default is rsa:2048.''', default="rsa:2048")
 

	
 
    def init_wrapper(args):
 
        project_directory = os.getcwd()
 
        if args.ca_base_name is None:
gimmecert/commands.py
Show inline comments
 
@@ -44,13 +44,13 @@ class InvalidCommandInvocation(Exception):
 
    """
 
    Exception thrown if command is invoked with invalid arguments.
 
    """
 
    pass
 

	
 

	
 
def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth, key_generator):
 
def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth, key_specification):
 
    """
 
    Initialises the necessary directory and CA hierarchies for use in
 
    the specified directory.
 

	
 
    :param stdout: Output stream where the informative messages should be written-out.
 
    :type stdout: io.IOBase
 
@@ -64,14 +64,14 @@ def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth, ke
 
    :param ca_base_name: Base name to use for constructing CA subject DNs.
 
    :type ca_base_name: str
 

	
 
    :param ca_hierarchy_depth: Length/depths of CA hierarchy that should be initialised. E.g. total number of CAs in chain.
 
    :type ca_hierarchy_depth: int
 

	
 
    :param key_generator: Callable for generating private keys.
 
    :type key_generator: callable[[], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]
 
    :param key_specification: Key specification to use when generating private keys for the hierarchy.
 
    :type key_specification: tuple(str, int)
 

	
 
    :returns: Status code, one from gimmecert.commands.ExitCode.
 
    :rtype: int
 
    """
 

	
 
    # Set-up various paths.
 
@@ -83,12 +83,13 @@ def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth, ke
 
        return ExitCode.ERROR_ALREADY_INITIALISED
 

	
 
    # Initialise the directory.
 
    gimmecert.storage.initialise_storage(project_directory)
 

	
 
    # Generate the CA hierarchy.
 
    key_generator = gimmecert.crypto.KeyGenerator(key_specification[0], key_specification[1])
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy(ca_base_name, ca_hierarchy_depth, key_generator)
 

	
 
    # Output the CA private keys and certificates.
 
    for level, (private_key, certificate) in enumerate(ca_hierarchy, 1):
 
        private_key_path = os.path.join(ca_directory, 'level%d.key.pem' % level)
 
        certificate_path = os.path.join(ca_directory, 'level%d.cert.pem' % level)
gimmecert/crypto.py
Show inline comments
 
@@ -33,41 +33,30 @@ class KeyGenerator:
 

	
 
    Instances are callable objects that generate and return the
 
    private key according to key specification passed-in during the
 
    instance initialisation.
 
    """
 

	
 
    def __init__(self, specification):
 
    def __init__(self, algorithm, parameters):
 
        """
 
        Initialises an instance.
 

	
 
        :param specification: Specification describing the private keys that that instance should be generating.
 
                              For RSA keys, use syntax "rsa:BIT_LENGTH".
 
        :type specification: str
 
        :param algorithm: Algorithm to use. Supported algorithms: 'rsa'.
 
        :type algorithm: str
 

	
 
        :raises ValueError: If passed-in specification is invalid.
 
        :param parameters: Parameters for generating the keys using the specified algorithm. For RSA keys this is key size.
 
        :type parameters: int
 
        """
 

	
 
        try:
 
            # This will throw ValueError if we can't get two values
 
            # assigned via split.
 
            key_type, key_parameters = specification.split(":", 2)
 

	
 
            if key_type == "rsa" and key_parameters.isnumeric():
 
                self._algorithm = "rsa"
 
                self._parameters = int(key_parameters)
 
            else:
 
                raise ValueError()
 

	
 
        except ValueError:
 
            raise ValueError("Invalid key specification: '%s'" % specification)
 
        self._algorithm = algorithm
 
        self._parameters = parameters
 

	
 
    def __str__(self):
 
        """
 
        Returns string (human-readable) representation of stored key
 
        algorithm and parameters.
 
        Returns string (human-readable) representation of stored algorithm
 
        and parameters.
 

	
 
        :returns: String representation of object.
 
        :rtype: str
 
        """
 

	
 
        return "%d-bit RSA" % self._parameters
tests/conftest.py
Show inline comments
 
@@ -140,13 +140,13 @@ def sample_project_directory(tmpdir):
 
        private_key = gimmecert.crypto.generate_private_key()
 
        csr = gimmecert.crypto.generate_csr(name, private_key)
 
        gimmecert.storage.write_private_key(private_key, custom_csr_dir.join("%s.key.pem" % name).strpath)
 
        gimmecert.storage.write_csr(csr, custom_csr_dir.join("%s.csr.pem" % name).strpath)
 

	
 
    # Initialise one-level deep hierarchy.
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    # Issue a bunch of certificates.
 
    for i in range(1, per_type_count + 1):
 
        entity_name = "server-with-privkey-%d" % i
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, entity_name, None, None)
 

	
 
@@ -183,9 +183,9 @@ def gctmpdir(tmpdir):
 

	
 
    :returs: Parent directory where Gimmecert has been initialised. Essentially the tmpdir fixture.
 
    :rtype: py.path.local
 
    """
 

	
 
    # Initialise one-level deep hierarchy.
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    return tmpdir
tests/test_cli.py
Show inline comments
 
@@ -373,47 +373,40 @@ def test_command_exists_and_accepts_help_flag(tmpdir, command, help_option):
 

	
 
        assert e_info.value.code == 0
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'init'])
 
@mock.patch('gimmecert.cli.init')
 
@mock.patch('gimmecert.cli.KeyGenerator')
 
def test_init_command_invoked_with_correct_parameters_no_options(mock_key_generator, mock_init, tmpdir):
 
def test_init_command_invoked_with_correct_parameters_no_options(mock_init, tmpdir):
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
    tmpdir.chdir()
 

	
 
    mock_init.return_value = gimmecert.commands.ExitCode.SUCCESS
 

	
 
    default_depth = 1
 

	
 
    mock_key_generator.return_value = mock.Mock()
 

	
 
    gimmecert.cli.main()
 

	
 
    mock_key_generator.assert_called_once_with("rsa:2048")
 
    mock_init.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, tmpdir.basename, default_depth, mock_key_generator.return_value)
 
    mock_init.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, tmpdir.basename, default_depth, ('rsa', 2048))
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'init', '-b', 'My Project'])
 
@mock.patch('sys.argv', ['gimmecert', 'init', '-b', 'My Project', '-k', 'rsa:4096'])
 
@mock.patch('gimmecert.cli.init')
 
@mock.patch('gimmecert.cli.KeyGenerator')
 
def test_init_command_invoked_with_correct_parameters_with_options(mock_key_generator, mock_init, tmpdir):
 
def test_init_command_invoked_with_correct_parameters_with_options(mock_init, tmpdir):
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
    tmpdir.chdir()
 

	
 
    mock_init.return_value = gimmecert.commands.ExitCode.SUCCESS
 

	
 
    default_depth = 1
 

	
 
    mock_key_generator.return_value = mock.Mock()
 

	
 
    gimmecert.cli.main()
 

	
 
    mock_init.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'My Project', default_depth, mock_key_generator.return_value)
 
    mock_init.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'My Project', default_depth, ('rsa', 4096))
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'server'])
 
def test_setup_server_subcommand_fails_without_arguments(tmpdir):
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
@@ -706,6 +699,32 @@ def test_renew_command_fails_if_both_new_private_key_and_csr_options_are_specifi
 

	
 
    with pytest.raises(SystemExit) as e_info:
 
        gimmecert.cli.main()
 

	
 
    assert mock_renew.called is False
 
    assert e_info.value.code != 0
 

	
 

	
 
@pytest.mark.parametrize("key_specification", [
 
    "",
 
    "rsa",
 
    "rsa:not_a_number",
 
    "unsupported:algorithm",
 
])
 
def test_key_specification_raises_exception_for_invalid_specification(key_specification):
 

	
 
    with pytest.raises(ValueError) as e_info:
 
        gimmecert.cli.key_specification(key_specification)
 

	
 
    assert str(e_info.value) == "Invalid key specification: '%s'" % key_specification
 

	
 

	
 
@pytest.mark.parametrize("key_specification, expected_return_value", [
 
    ("rsa:1024", ("rsa", 1024)),
 
    ("rsa:2048", ("rsa", 2048)),
 
    ("rsa:4096", ("rsa", 4096)),
 
])
 
def test_key_specification_returns_algorithm_and_parameters_for_valid_specification(key_specification, expected_return_value):
 

	
 
    algorithm, parameters = gimmecert.cli.key_specification(key_specification)  # should not raise
 

	
 
    assert (algorithm, parameters) == expected_return_value
tests/test_commands.py
Show inline comments
 
@@ -35,82 +35,82 @@ from freezegun import freeze_time
 

	
 
def test_init_sets_up_directory_structure(tmpdir):
 
    base_dir = tmpdir.join('.gimmecert')
 
    ca_dir = tmpdir.join('.gimmecert', 'ca')
 
    server_dir = tmpdir.join('.gimmecert', 'server')
 

	
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    assert os.path.exists(base_dir.strpath)
 
    assert os.path.exists(ca_dir.strpath)
 
    assert os.path.exists(server_dir.strpath)
 

	
 

	
 
def test_init_generates_single_ca_artifact_for_depth_1(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').strpath)
 

	
 

	
 
def test_init_generates_three_ca_artifacts_for_depth_3(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, ("rsa", 2048))
 

	
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level2.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level2.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level3.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level3.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').strpath)
 

	
 

	
 
def test_init_outputs_full_chain_for_depth_1(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    level1_certificate = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    full_chain = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 
    assert level1_certificate == full_chain
 
    assert full_chain.replace(level1_certificate, '') == ''
 

	
 

	
 
def test_init_outputs_full_chain_for_depth_3(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, ("rsa", 2048))
 

	
 
    level1_certificate = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    level2_certificate = tmpdir.join('.gimmecert', 'ca', 'level2.cert.pem').read()
 
    level3_certificate = tmpdir.join('.gimmecert', 'ca', 'level3.cert.pem').read()
 
    full_chain = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 
    assert level1_certificate in full_chain
 
    assert level2_certificate in full_chain
 
    assert level3_certificate in full_chain
 
    assert full_chain == "%s\n%s\n%s" % (level1_certificate, level2_certificate, level3_certificate)
 

	
 

	
 
def test_init_returns_success_if_directory_has_not_been_previously_initialised(tmpdir):
 
    status_code = gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    status_code = gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    assert status_code == gimmecert.commands.ExitCode.SUCCESS
 

	
 

	
 
def test_init_returns_error_code_if_directory_has_been_previously_initialised(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    status_code = gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 
    status_code = gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    assert status_code == gimmecert.commands.ExitCode.ERROR_ALREADY_INITIALISED
 

	
 

	
 
def test_init_does_not_overwrite_artifcats_if_already_initialised(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    level1_private_key_before = tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').read()
 
    level1_certificate_before = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    full_chain_before = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 

	
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 

	
 
    level1_private_key_after = tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').read()
 
    level1_certificate_after = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    full_chain_after = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 

	
 
    assert level1_private_key_before == level1_private_key_after
 
@@ -247,13 +247,13 @@ def test_server_errors_out_if_certificate_already_issued(gctmpdir):
 

	
 

	
 
def test_init_command_stdout_and_stderr_for_single_ca(tmpdir):
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    gimmecert.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 1, ("rsa", 2048))
 

	
 
    stdout = stdout_stream.getvalue()
 
    stderr = stderr_stream.getvalue()
 

	
 
    assert stderr == ""
 
    assert "CA hierarchy initialised using 2048-bit RSA keys" in stdout
 
@@ -263,13 +263,13 @@ def test_init_command_stdout_and_stderr_for_single_ca(tmpdir):
 

	
 

	
 
def test_init_command_stdout_and_stderr_for_multiple_cas_with_rsa_1024(tmpdir):
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    gimmecert.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 3, gimmecert.crypto.KeyGenerator("rsa:1024"))
 
    gimmecert.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 3, ("rsa", 1024))
 

	
 
    stdout = stdout_stream.getvalue()
 
    stderr = stderr_stream.getvalue()
 

	
 
    assert stderr == ""
 
    assert "CA hierarchy initialised using 1024-bit RSA keys" in stdout
 
@@ -283,15 +283,15 @@ def test_init_command_stdout_and_stderr_for_multiple_cas_with_rsa_1024(tmpdir):
 

	
 

	
 
def test_init_command_stdout_and_stderr_if_hierarchy_already_initialised(tmpdir):
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, "myproject", 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, "myproject", 1, ("rsa", 2048))
 

	
 
    gimmecert.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 1, ("rsa", 2048))
 

	
 
    stdout = stdout_stream.getvalue()
 
    stderr = stderr_stream.getvalue()
 

	
 
    assert "CA hierarchy has already been initialised" in stderr
 
    assert stdout == ""
 
@@ -625,13 +625,13 @@ def test_status_reports_uninitialised_directory(tmpdir):
 

	
 
def test_status_reports_ca_hierarchy_information(tmpdir):
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    with freeze_time('2018-01-01 00:15:00'):
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, ("rsa", 2048))
 

	
 
    with freeze_time('2018-06-01 00:15:00'):
 
        status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath)
 

	
 
    stdout = stdout_stream.getvalue()
 
    stdout_lines = stdout.split("\n")
 
@@ -674,13 +674,13 @@ def test_status_reports_server_certificate_information(tmpdir):
 
    myserver3_csr_file = tmpdir.join('server3.csr.pem')
 
    myserver3_private_key = gimmecert.crypto.generate_private_key()
 
    myserver3_csr = gimmecert.crypto.generate_csr('blah', myserver3_private_key)
 
    gimmecert.storage.write_csr(myserver3_csr, myserver3_csr_file.strpath)
 

	
 
    with freeze_time('2018-01-01 00:15:00'):
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, ("rsa", 2048))
 

	
 
    with freeze_time('2018-02-01 00:15:00'):
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver1', None, None)
 

	
 
    with freeze_time('2018-03-01 00:15:00'):
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver2', ['myservice1.example.com', 'myservice2.example.com'], None)
 
@@ -742,13 +742,13 @@ def test_status_reports_client_certificate_information(tmpdir):
 
    myclient3_csr_file = tmpdir.join('client3.csr.pem')
 
    myclient3_private_key = gimmecert.crypto.generate_private_key()
 
    myclient3_csr = gimmecert.crypto.generate_csr('blah', myclient3_private_key)
 
    gimmecert.storage.write_csr(myclient3_csr, myclient3_csr_file.strpath)
 

	
 
    with freeze_time('2018-01-01 00:15:00'):
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, ("rsa", 2048))
 

	
 
    with freeze_time('2018-02-01 00:15:00'):
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None)
 

	
 
    with freeze_time('2018-03-01 00:15:00'):
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None)
 
@@ -800,13 +800,13 @@ def test_status_reports_client_certificate_information(tmpdir):
 
def test_status_reports_no_server_certificates_were_issued(tmpdir):
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    # Just create some sample data, but no server certificates.
 
    with freeze_time('2018-01-01 00:15:00'):
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None)
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None)
 

	
 
    status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath)
 

	
 
    stdout = stdout_stream.getvalue()
 
@@ -821,13 +821,13 @@ def test_status_reports_no_server_certificates_were_issued(tmpdir):
 
def test_status_reports_no_client_certificates_were_issued(tmpdir):
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    # Just create some sample data, but no client certificates.
 
    with freeze_time('2018-01-01 00:15:00'):
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 2048))
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver1', None, None)
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver2', None, None)
 

	
 
    status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath)
 

	
 
    stdout = stdout_stream.getvalue()
 
@@ -860,13 +860,13 @@ def test_certificate_marked_as_not_valid_or_expired_as_appropriate(tmpdir, subje
 

	
 
    stdout_stream = io.StringIO()
 
    stderr_stream = io.StringIO()
 

	
 
    # Perform action on our fixed issuance date.
 
    with freeze_time(issuance_date):
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, "My Project", 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, "My Project", 1, ("rsa", 2048))
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver', None, None)
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', None)
 

	
 
    # Move to specific date in future/past for different validity checks.
 
    with freeze_time(status_date):
 
        status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath)
tests/test_crypto.py
Show inline comments
 
@@ -105,56 +105,56 @@ def test_issue_certificate_has_correct_content():
 

	
 

	
 
def test_generate_ca_hierarchy_returns_list_with_3_elements_for_depth_3():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    assert isinstance(hierarchy, list)
 
    assert len(hierarchy) == depth
 

	
 

	
 
def test_generate_ca_hierarchy_returns_list_with_1_element_for_depth_1():
 
    base_name = 'My Project'
 
    depth = 1
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    assert isinstance(hierarchy, list)
 
    assert len(hierarchy) == depth
 

	
 

	
 
def test_generate_ca_hierarchy_returns_list_of_private_key_certificate_pairs():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    for private_key, certificate in hierarchy:
 
        assert isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey)
 
        assert isinstance(certificate, cryptography.x509.Certificate)
 

	
 

	
 
def test_generate_ca_hierarchy_subject_dns_have_correct_value():
 
    base_name = 'My Project'
 
    depth = 3
 
    key_generator = gimmecert.crypto.KeyGenerator("rsa:2048")
 
    key_generator = gimmecert.crypto.KeyGenerator("rsa", 2048)
 

	
 
    level1, level2, level3 = [certificate for _, certificate in gimmecert.crypto.generate_ca_hierarchy(base_name, depth, key_generator)]
 

	
 
    assert level1.subject == cryptography.x509.Name(gimmecert.crypto.get_dn('My Project Level 1 CA'))
 
    assert level2.subject == cryptography.x509.Name(gimmecert.crypto.get_dn('My Project Level 2 CA'))
 
    assert level3.subject == cryptography.x509.Name(gimmecert.crypto.get_dn('My Project Level 3 CA'))
 

	
 

	
 
def test_generate_ca_hierarchy_issuer_dns_have_correct_value():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    level1_key, level1_certificate = hierarchy[0]
 
    level2_key, level2_certificate = hierarchy[1]
 
    level3_key, level3_certificate = hierarchy[2]
 

	
 
    assert level1_certificate.issuer == cryptography.x509.Name(gimmecert.crypto.get_dn('My Project Level 1 CA'))
 
@@ -163,13 +163,13 @@ def test_generate_ca_hierarchy_issuer_dns_have_correct_value():
 

	
 

	
 
def test_generate_ca_hierarchy_private_keys_match_with_public_keys_in_certificates():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    level1_private_key, level1_certificate = hierarchy[0]
 
    level2_private_key, level2_certificate = hierarchy[1]
 
    level3_private_key, level3_certificate = hierarchy[2]
 

	
 
    assert level1_private_key.public_key().public_numbers() == level1_certificate.public_key().public_numbers()
 
@@ -178,13 +178,13 @@ def test_generate_ca_hierarchy_private_keys_match_with_public_keys_in_certificat
 

	
 

	
 
def test_generate_ca_hierarchy_cas_have_differing_keys():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    level1_private_key, _ = hierarchy[0]
 
    level2_private_key, _ = hierarchy[1]
 
    level3_private_key, _ = hierarchy[2]
 

	
 
    level1_public_numbers = level1_private_key.public_key().public_numbers()
 
@@ -197,13 +197,13 @@ def test_generate_ca_hierarchy_cas_have_differing_keys():
 

	
 

	
 
def test_generate_ca_hierarchy_certificates_have_same_validity():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    _, level1_certificate = hierarchy[0]
 
    _, level2_certificate = hierarchy[1]
 
    _, level3_certificate = hierarchy[2]
 

	
 
    assert level1_certificate.not_valid_before == level2_certificate.not_valid_before == level3_certificate.not_valid_before
 
@@ -247,37 +247,37 @@ def test_issue_certificate_sets_no_extensions_if_none_are_passed():
 

	
 

	
 
def test_generate_ca_hierarchy_produces_certificates_with_ca_basic_constraints():
 
    base_name = 'My test'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    for _, certificate in hierarchy:
 
        stored_extension = certificate.extensions.get_extension_for_class(cryptography.x509.BasicConstraints)
 
        value, critical = stored_extension.value, stored_extension.critical
 

	
 
        assert isinstance(value, cryptography.x509.BasicConstraints)
 
        assert critical is True
 
        assert value.ca is True
 
        assert value.path_length is None
 

	
 

	
 
def test_issue_server_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert isinstance(certificate, cryptography.x509.Certificate)
 

	
 

	
 
def test_issue_server_certificate_sets_correct_extensions():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    expected_basic_constraints = cryptography.x509.BasicConstraints(ca=False, path_length=None)
 
    expected_key_usage = cryptography.x509.KeyUsage(
 
@@ -316,49 +316,49 @@ def test_issue_server_certificate_sets_correct_extensions():
 

	
 
    assert certificate.extensions.get_extension_for_class(cryptography.x509.SubjectAlternativeName).critical is False
 
    assert certificate.extensions.get_extension_for_class(cryptography.x509.SubjectAlternativeName).value == expected_subject_alternative_name
 

	
 

	
 
def test_issue_server_certificate_has_correct_issuer_and_subject():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 4, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 4, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[3]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.issuer == issuer_certificate.subject
 
    assert certificate.subject == gimmecert.crypto.get_dn('myserver')
 

	
 

	
 
def test_issue_server_certificate_has_correct_public_key():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.public_key().public_numbers() == private_key.public_key().public_numbers()
 

	
 

	
 
@freeze_time('2018-01-01 00:15:00')
 
def test_issue_server_certificate_not_before_is_15_minutes_in_past():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_before == datetime.datetime(2018, 1, 1, 0, 0)
 

	
 

	
 
def test_issue_server_certificate_not_before_does_not_exceed_ca_validity():
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    with freeze_time(issuer_certificate.not_valid_before - datetime.timedelta(seconds=1)):
 
@@ -366,26 +366,26 @@ def test_issue_server_certificate_not_before_does_not_exceed_ca_validity():
 

	
 
    assert certificate1.not_valid_before == issuer_certificate.not_valid_before
 

	
 

	
 
def test_issue_server_certificate_not_after_does_not_exceed_ca_validity():
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    with freeze_time(issuer_certificate.not_valid_after + datetime.timedelta(seconds=1)):
 
        certificate1 = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate1.not_valid_after == issuer_certificate.not_valid_after
 

	
 

	
 
def test_issue_server_certificate_incorporates_additional_dns_subject_alternative_names():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    expected_subject_alternative_name = cryptography.x509.SubjectAlternativeName(
 
        [
 
@@ -400,36 +400,36 @@ def test_issue_server_certificate_incorporates_additional_dns_subject_alternativ
 

	
 
    assert certificate.extensions.get_extension_for_class(cryptography.x509.SubjectAlternativeName).critical is False
 
    assert certificate.extensions.get_extension_for_class(cryptography.x509.SubjectAlternativeName).value == expected_subject_alternative_name
 

	
 

	
 
def test_issue_client_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert isinstance(certificate, cryptography.x509.Certificate)
 

	
 

	
 
def test_issue_client_certificate_has_correct_issuer_and_subject():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 4, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 4, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[3]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.issuer == issuer_certificate.subject
 
    assert certificate.subject == gimmecert.crypto.get_dn('myclient')
 

	
 

	
 
def test_issue_client_certificate_sets_correct_extensions():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    expected_basic_constraints = cryptography.x509.BasicConstraints(ca=False, path_length=None)
 
    expected_key_usage = cryptography.x509.KeyUsage(
 
@@ -460,37 +460,37 @@ def test_issue_client_certificate_sets_correct_extensions():
 

	
 
    assert certificate.extensions.get_extension_for_class(cryptography.x509.ExtendedKeyUsage).critical is True
 
    assert certificate.extensions.get_extension_for_class(cryptography.x509.ExtendedKeyUsage).value == expected_extended_key_usage
 

	
 

	
 
def test_issue_client_certificate_has_correct_public_key():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.public_key().public_numbers() == private_key.public_key().public_numbers()
 

	
 

	
 
@freeze_time('2018-01-01 00:15:00')
 
def test_issue_client_certificate_not_before_is_15_minutes_in_past():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_before == datetime.datetime(2018, 1, 1, 0, 0)
 

	
 

	
 
def test_issue_client_certificate_not_before_does_not_exceed_ca_validity():
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    with freeze_time(issuer_certificate.not_valid_before - datetime.timedelta(seconds=1)):
 
@@ -498,38 +498,38 @@ def test_issue_client_certificate_not_before_does_not_exceed_ca_validity():
 

	
 
    assert certificate1.not_valid_before == issuer_certificate.not_valid_before
 

	
 

	
 
def test_issue_client_certificate_not_after_does_not_exceed_ca_validity():
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    with freeze_time(issuer_certificate.not_valid_after + datetime.timedelta(seconds=1)):
 
        certificate1 = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate1.not_valid_after == issuer_certificate.not_valid_after
 

	
 

	
 
def test_renew_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 
    old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    new_certificate = gimmecert.crypto.renew_certificate(old_certificate, private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert isinstance(new_certificate, cryptography.x509.Certificate)
 

	
 

	
 
def test_renew_certificate_has_correct_content():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.generate_private_key()
 
    old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 
    public_key = gimmecert.crypto.generate_private_key().public_key()
 

	
 
@@ -543,13 +543,13 @@ def test_renew_certificate_has_correct_content():
 

	
 

	
 
def test_renew_certificate_not_before_is_15_minutes_in_past():
 

	
 
    # Initial server certificate.
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
        issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
        private_key = gimmecert.crypto.generate_private_key()
 
        old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    # Renew certificate.
 
@@ -560,13 +560,13 @@ def test_renew_certificate_not_before_is_15_minutes_in_past():
 

	
 

	
 
def test_renew_certificate_not_before_does_not_exceed_ca_validity():
 

	
 
    # Initial server certificate.
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
        issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
        private_key = gimmecert.crypto.generate_private_key()
 
        old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    # Renew certificate.
 
@@ -577,13 +577,13 @@ def test_renew_certificate_not_before_does_not_exceed_ca_validity():
 

	
 

	
 
def test_renew_certificate_not_after_does_not_exceed_ca_validity():
 

	
 
    # Initial server certificate.
 
    with freeze_time('2018-01-01 00:15:00'):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
        issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
        private_key = gimmecert.crypto.generate_private_key()
 
        old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    # Renew certificate.
 
@@ -615,73 +615,37 @@ def test_generate_csr_returns_csr_with_passed_in_name():
 
    csr = gimmecert.crypto.generate_csr(name, private_key)
 

	
 
    assert csr.public_key().public_numbers() == private_key.public_key().public_numbers()
 
    assert csr.subject == expected_subject_dn
 

	
 

	
 
@pytest.mark.parametrize("key_specification", [
 
    "",
 
    "rsa",
 
    "rsa:not_a_number",
 
    "unsupported:algorithm",
 
@pytest.mark.parametrize("algorithm, parameters, string_representation", [
 
    ("rsa", 1024, "1024-bit RSA"),
 
    ("rsa", 2048, "2048-bit RSA"),
 
    ("rsa", 4096, "4096-bit RSA"),
 
])
 
def test_KeyGenerator_raises_exception_for_invalid_specification(key_specification):
 
def test_KeyGenerator_string_representation(algorithm, parameters, string_representation):
 

	
 
    with pytest.raises(ValueError) as e_info:
 
        gimmecert.crypto.KeyGenerator(key_specification)
 

	
 
    assert str(e_info.value) == "Invalid key specification: '%s'" % key_specification
 

	
 

	
 
@pytest.mark.parametrize("key_specification", [
 
    "rsa:1024",
 
    "rsa:2048",
 
    "rsa:4096",
 
])
 
def test_KeyGenerator_accepts_valid_specifications(key_specification):
 

	
 
    gimmecert.crypto.KeyGenerator(key_specification)  # should not raise
 

	
 

	
 
def test_KeyGenerator_stores_specification():
 

	
 
    key_generator = gimmecert.crypto.KeyGenerator("rsa:2048")
 

	
 
    assert key_generator._algorithm == "rsa"
 
    assert key_generator._parameters == 2048
 

	
 

	
 
@pytest.mark.parametrize("key_specification, string_representation", [
 
    ("rsa:1024", "1024-bit RSA"),
 
    ("rsa:2048", "2048-bit RSA"),
 
    ("rsa:4096", "4096-bit RSA"),
 
])
 
def test_KeyGenerator_string_representation(key_specification, string_representation):
 

	
 
    key_generator = gimmecert.crypto.KeyGenerator(key_specification)
 
    key_generator = gimmecert.crypto.KeyGenerator(algorithm, parameters)
 
    assert str(key_generator) == string_representation
 

	
 

	
 
def test_KeyGenerator_instance_returns_rsa_private_key():
 

	
 
    key_generator_1 = gimmecert.crypto.KeyGenerator("rsa:1024")
 
    key_generator_2 = gimmecert.crypto.KeyGenerator("rsa:2048")
 
@pytest.mark.parametrize("key_size", [1024, 2048, 4096])
 
def test_KeyGenerator_instance_returns_rsa_private_key_of_correct_size(key_size):
 

	
 
    private_key_1 = key_generator_1()
 
    private_key_2 = key_generator_2()
 
    key_generator = gimmecert.crypto.KeyGenerator("rsa", key_size)
 

	
 
    assert isinstance(private_key_1, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey)
 
    assert isinstance(private_key_2, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey)
 
    private_key = key_generator()
 

	
 
    assert private_key_1.key_size == 1024
 
    assert private_key_2.key_size == 2048
 
    assert isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey)
 
    assert private_key.key_size == key_size
 

	
 

	
 
@pytest.mark.parametrize("key_generator, expected_bit_size", [
 
    (gimmecert.crypto.KeyGenerator("rsa:1024"), 1024),
 
    (gimmecert.crypto.KeyGenerator("rsa:2048"), 2048),
 
    (gimmecert.crypto.KeyGenerator("rsa", 1024), 1024),
 
    (gimmecert.crypto.KeyGenerator("rsa", 2048), 2048),
 
])
 
def test_generate_ca_hierarchy_uses_correct_rsa_bit_size(key_generator, expected_bit_size):
 
    base_name = "My Test"
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, key_generator)
tests/test_storage.py
Show inline comments
 
@@ -77,13 +77,13 @@ def test_write_certificate(tmpdir):
 
        assert 'BEGIN CERTIFICATE' in content
 
        assert 'END CERTIFICATE' in content
 

	
 

	
 
def test_write_certificate_chain(tmpdir):
 
    output_file = tmpdir.join('chain.cert.pem')
 
    certificate_chain = [certificate for _, certificate in gimmecert.crypto.generate_ca_hierarchy('My Project', 3, gimmecert.crypto.KeyGenerator("rsa:2048"))]
 
    certificate_chain = [certificate for _, certificate in gimmecert.crypto.generate_ca_hierarchy('My Project', 3, gimmecert.crypto.KeyGenerator("rsa", 2048))]
 
    level1_pem, level2_pem, level3_pem = [gimmecert.utils.certificate_to_pem(certificate) for certificate in certificate_chain]
 

	
 
    gimmecert.storage.write_certificate_chain(certificate_chain, output_file.strpath)
 
    content = output_file.read(mode='r')
 
    expected_content = "%s\n%s\n%s" % (level1_pem, level2_pem, level3_pem)
 

	
 
@@ -103,13 +103,13 @@ def test_is_initialised_returns_false_if_directory_is_not_initialised(tmpdir):
 

	
 
    assert gimmecert.storage.is_initialised(tmpdir.strpath) is False
 

	
 

	
 
def test_read_ca_hierarchy_returns_list_of_ca_private_key_and_certificate_pairs_for_single_ca(tmpdir):
 
    tmpdir.chdir()
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, 'My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, 'My Project', 1, ("rsa", 2048))
 

	
 
    ca_hierarchy = gimmecert.storage.read_ca_hierarchy(tmpdir.join('.gimmecert', 'ca').strpath)
 

	
 
    assert len(ca_hierarchy) == 1
 

	
 
    private_key, certificate = ca_hierarchy[0]
 
@@ -143,13 +143,13 @@ def test_read_certificate_returns_certificate(tmpdir):
 
    assert isinstance(my_certificate, cryptography.x509.Certificate)
 
    assert my_certificate == certificate
 

	
 

	
 
def test_read_ca_hierarchy_returns_list_of_ca_private_key_and_certificate_pairs_in_hierarchy_order_for_multiple_cas(tmpdir):
 
    tmpdir.chdir()
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, 'My Project', 4, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, 'My Project', 4, ("rsa", 2048))
 

	
 
    ca_hierarchy = gimmecert.storage.read_ca_hierarchy(tmpdir.join('.gimmecert', 'ca').strpath)
 

	
 
    assert len(ca_hierarchy) == 4
 

	
 
    private_key_1, certificate_1 = ca_hierarchy[0]
tests/test_utils.py
Show inline comments
 
@@ -72,13 +72,13 @@ def test_date_range_to_str():
 

	
 
    assert isinstance(representation, str)
 
    assert representation == "2017-01-02 03:04:05 UTC - 2018-06-07 08:09:10 UTC"
 

	
 

	
 
def test_get_dns_names_returns_empty_list_if_no_dns_names():
 
    issuer_private_key, issuer_certificate = gimmecert.crypto.generate_ca_hierarchy('My Test', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))[0]
 
    issuer_private_key, issuer_certificate = gimmecert.crypto.generate_ca_hierarchy('My Test', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))[0]
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_client_certificate(
 
        'myclient', private_key.public_key(),
 
        issuer_private_key, issuer_certificate
 
    )
 
@@ -88,13 +88,13 @@ def test_get_dns_names_returns_empty_list_if_no_dns_names():
 
    assert isinstance(dns_names, list)
 
    assert dns_names == []
 

	
 

	
 
def test_get_dns_names_returns_list_of_dns_names():
 

	
 
    issuer_private_key, issuer_certificate = gimmecert.crypto.generate_ca_hierarchy('My Test', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))[0]
 
    issuer_private_key, issuer_certificate = gimmecert.crypto.generate_ca_hierarchy('My Test', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))[0]
 
    private_key = gimmecert.crypto.generate_private_key()
 

	
 
    certificate = gimmecert.crypto.issue_server_certificate(
 
        'myserver', private_key.public_key(),
 
        issuer_private_key, issuer_certificate,
 
        extra_dns_names=['myservice1.example.com', 'myservice2.example.com']
0 comments (0 inline, 0 general)