Changeset - 52d85e47faa0
[Not reviewed]
0 10 0
Branko Majic (branko) - 4 years ago 2020-06-01 16:23:21
branko@majic.rs
GC-37: Added support for requesting custom RSA key size when initialising the CA hierarchy:

- Added functional test.
- Added unit tests.
- Added new CLI option for specifying the algorithm.
- Implemented KeyGenerator factory-like class that can be called to
generate a private key with desired specification.
- The init init function now accepts a callable that is used to
generate private keys.
- The generate_ca_hierarchy function now accepts a callable that is
used to generate private keys.
- Updated existing unit tests to cope with changes to the init
and generate_ca_hierarchy function signatures.
- Updated existing unit tests to cope with changes to existing
functionality.
- Updated existing functional tests to cope with changes in command
output.
10 files changed with 339 insertions and 74 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_init.py
Show inline comments
 
@@ -58,7 +58,7 @@ def test_initialisation_on_fresh_directory(tmpdir):
 
    # text to John that the directory has been initialised.
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "CA hierarchy initialised" in stdout
 
    assert "CA hierarchy initialised using 2048-bit RSA keys." in stdout
 

	
 
    # The tool also points John to generated key and certificate material.
 
    assert ".gimmecert/ca/level1.key.pem" in stdout
 
@@ -149,7 +149,7 @@ def test_initialisation_with_custom_base_name(tmpdir):
 
    # his CA hierarchy has been initialised..
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "CA hierarchy initialised." in stdout
 
    assert "CA hierarchy initialised using 2048-bit RSA keys." in stdout
 

	
 
    # Just before he starts using the CA certificates further, he
 
    # decides to double-check the results. He runs a couple of
 
@@ -193,7 +193,7 @@ def test_initialisation_with_custom_hierarchy_depth(tmpdir):
 
    # more CA artifacts listed now.
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "CA hierarchy initialised." in stdout
 
    assert "CA hierarchy initialised using 2048-bit RSA keys." in stdout
 
    assert ".gimmecert/ca/level1.key.pem" in stdout
 
    assert ".gimmecert/ca/level1.cert.pem" in stdout
 
    assert ".gimmecert/ca/level2.key.pem" in stdout
 
@@ -284,3 +284,55 @@ def test_initialisation_with_custom_hierarchy_depth(tmpdir):
 

	
 
    # He is happy to see that verification succeeds.
 
    assert error_code == 0
 

	
 

	
 
def test_initialisation_with_rsa_private_key_specificiation(tmpdir):
 
    # John is looking into improving the security of one of his
 
    # projects. Amongst other things, John is interested in using
 
    # stronger private keys for his TLS services - which he wants to
 
    # try out in his test envioronment first.
 

	
 
    # John knows that the Gimmecert tool uses 2048-bit RSA keys for
 
    # the CA hierarchy, but what he would really like to do is specify
 
    # himself what kind of private key should be generated
 
    # instead. He checks-out the help for the init command first.
 
    stdout, _, _ = run_command('gimmecert', 'init', '-h')
 

	
 
    # John noticies there is an option to provide a custom key
 
    # specification to the tool, that he can specify the length of
 
    # the RSA private keys, and that the default is "rsa:2048".
 
    assert "--key-specification" in stdout
 
    assert " -k" in stdout
 
    assert "rsa:BIT_LENGTH" in stdout
 
    assert "Default is rsa:2048" in stdout
 

	
 
    # John switches to his project directory.
 
    tmpdir.chdir()
 

	
 
    # He initalises the CA hierarchy, requesting to use 4096-bit RSA
 
    # keys.
 
    stdout, stderr, exit_code = run_command('gimmecert', 'init', '--key-specification', 'rsa:4096')
 

	
 
    # Command finishes execution with success, and John notices that
 
    # the tool has informed him of what the private key algorithm is
 
    # in use for the CA hierarchy.
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "CA hierarchy initialised using 4096-bit RSA keys." in stdout
 

	
 
    # John goes ahead and inspects the CA private key to ensure his
 
    # private key specification has been accepted.
 
    stdout, stderr, exit_code = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/ca/level1.key.pem')
 

	
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "Private-Key: (4096 bit)" in stdout
 

	
 
    # John also does a quick check on the generated certificate's
 
    # signing and public key algorithm.
 
    stdout, stderr, exit_code = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/ca/level1.cert.pem')
 

	
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "Signature Algorithm: sha256WithRSAEncryption" in stdout
 
    assert "Public-Key: (4096 bit)" in stdout
gimmecert/cli.py
Show inline comments
 
@@ -25,8 +25,10 @@ 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
 

	
 

	
 
@@ -81,13 +83,16 @@ 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,
 
                           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:
 
            args.ca_base_name = os.path.basename(project_directory)
 

	
 
        return init(sys.stdout, sys.stderr, project_directory, args.ca_base_name, args.ca_hierarchy_depth)
 
        return init(sys.stdout, sys.stderr, project_directory, args.ca_base_name, args.ca_hierarchy_depth, args.key_specification)
 

	
 
    subparser.set_defaults(func=init_wrapper)
 

	
gimmecert/commands.py
Show inline comments
 
@@ -33,6 +33,7 @@ class ExitCode:
 
    """
 

	
 
    SUCCESS = 0
 
    ERROR_ARGUMENTS = 2
 
    ERROR_ALREADY_INITIALISED = 10
 
    ERROR_NOT_INITIALISED = 11
 
    ERROR_CERTIFICATE_ALREADY_ISSUED = 12
 
@@ -46,7 +47,7 @@ class InvalidCommandInvocation(Exception):
 
    pass
 

	
 

	
 
def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth):
 
def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth, key_generator):
 
    """
 
    Initialises the necessary directory and CA hierarchies for use in
 
    the specified directory.
 
@@ -66,6 +67,9 @@ def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth):
 
    :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]
 

	
 
    :returns: Status code, one from gimmecert.commands.ExitCode.
 
    :rtype: int
 
    """
 
@@ -82,7 +86,7 @@ def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth):
 
    gimmecert.storage.initialise_storage(project_directory)
 

	
 
    # Generate the CA hierarchy.
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy(ca_base_name, ca_hierarchy_depth)
 
    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):
 
@@ -96,7 +100,7 @@ def init(stdout, stderr, project_directory, ca_base_name, ca_hierarchy_depth):
 
    full_chain_path = os.path.join(ca_directory, 'chain-full.cert.pem')
 
    gimmecert.storage.write_certificate_chain(full_chain, full_chain_path)
 

	
 
    print("CA hierarchy initialised. Generated artefacts:", file=stdout)
 
    print("CA hierarchy initialised using %s keys. Generated artefacts:" % str(key_generator), file=stdout)
 
    for level in range(1, ca_hierarchy_depth+1):
 
        print("    CA Level %d private key: .gimmecert/ca/level%d.key.pem" % (level, level), file=stdout)
 
        print("    CA Level %d certificate: .gimmecert/ca/level%d.cert.pem" % (level, level), file=stdout)
gimmecert/crypto.py
Show inline comments
 
@@ -25,6 +25,73 @@ import cryptography.x509
 
from dateutil.relativedelta import relativedelta
 

	
 

	
 
class KeyGenerator:
 
    """
 
    Provides abstract factory-like interface for generating private
 
    keys. Algorithm and parameters for the private key are provided
 
    during instance initialisation by passing-in a specification.
 

	
 
    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):
 
        """
 
        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
 

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

	
 
        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)
 

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

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

	
 
        return "%d-bit RSA" % self._parameters
 

	
 
    def __call__(self):
 
        """
 
        Generates RSA private key. Key size is deterimened by instance's
 
        key specification (passed-in during instance creation).
 

	
 
        :returns: RSA private key.
 
        :rtype: cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey
 
        """
 

	
 
        rsa_public_exponent = 65537
 

	
 
        private_key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
 
            public_exponent=rsa_public_exponent,
 
            key_size=self._parameters,
 
            backend=cryptography.hazmat.backends.default_backend()
 
        )
 

	
 
        return private_key
 

	
 

	
 
def generate_private_key():
 
    """
 
    Generates a 2048-bit RSA private key.
 
@@ -136,7 +203,7 @@ def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before
 
    return certificate
 

	
 

	
 
def generate_ca_hierarchy(base_name, depth):
 
def generate_ca_hierarchy(base_name, depth, key_generator):
 
    """
 
    Generates CA hierarchy with specified depth, using the provided
 
    naming as basis for the DNs.
 
@@ -144,6 +211,9 @@ def generate_ca_hierarchy(base_name, depth):
 
    :param base_name: Base name for constructing the CA DNs. Resulting DNs are of format 'BASE Level N'.
 
    :type base_name: str
 

	
 
    :param key_generator: Callable for generating private keys.
 
    :type key_generator: callable[[], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]
 

	
 
    :returns: List of CA private key and certificate pairs, starting with the level 1 (root) CA, and ending with the leaf CA.
 
    :rtype: list[(cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey, cryptography.x509.Certificate)]
 
    """
 
@@ -163,7 +233,7 @@ def generate_ca_hierarchy(base_name, depth):
 
    for level in range(1, depth+1):
 
        # Generate info for the new CA.
 
        dn = get_dn("%s Level %d CA" % (base_name, level))
 
        private_key = generate_private_key()
 
        private_key = key_generator()
 

	
 
        # First certificate issued needs to be self-signed.
 
        issuer_dn = issuer_dn or dn
tests/conftest.py
Show inline comments
 
@@ -86,7 +86,8 @@ def sample_project_directory(tmpdir):
 
    CSR).
 

	
 
    Initialised CA hierarchy is 1 level deep, with basename used being
 
    identical to temporary directory base name.
 
    identical to temporary directory base name, and it uses 2048-bit
 
    RSA keys.
 

	
 
    The following server certificates are issued:
 

	
 
@@ -142,7 +143,7 @@ def sample_project_directory(tmpdir):
 
        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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    # Issue a bunch of certificates.
 
    for i in range(1, per_type_count + 1):
 
@@ -170,7 +171,8 @@ def gctmpdir(tmpdir):
 
    simple CA hierarchy.
 

	
 
    Initialised CA hierarchy is 1 level deep, with basename used being
 
    identical to temporary directory base name.
 
    identical to temporary directory base name, and it uses 2048-bit
 
    RSA keys.
 

	
 
    The fixture is useful in testing of commands where the CA
 
    hierarchy does not matter (almost anything except init/status
 
@@ -184,6 +186,6 @@ def gctmpdir(tmpdir):
 
    """
 

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

	
 
    return tmpdir
tests/test_cli.py
Show inline comments
 
@@ -224,6 +224,10 @@ VALID_CLI_INVOCATIONS = [
 
    ("gimmecert.cli.init", ["gimmecert", "init", "--ca-hierarchy-depth", "3"]),
 
    ("gimmecert.cli.init", ["gimmecert", "init", "-d", "3"]),
 

	
 
    # init, key specification long and short option
 
    ("gimmecert.cli.init", ["gimmecert", "init", "--key-specification", "rsa:4096"]),
 
    ("gimmecert.cli.init", ["gimmecert", "init", "-k", "rsa:4096"]),
 

	
 
    # server, no options
 
    ("gimmecert.cli.server", ["gimmecert", "server", "myserver"]),
 

	
 
@@ -299,6 +303,52 @@ def test_parser_commands_and_options_are_available(tmpdir, command_function, cli
 
        gimmecert.cli.main()  # Should not raise
 

	
 

	
 
# List of _invalid_ CLI invocations to use in
 
# test_invalid_parser_commands_and_options_produce_error.
 
#
 
# Each element in this list should be a tuple where first element is
 
# the command function (relative to CLI module) that should be mocked,
 
# while second element is list of CLI arguments for invoking the
 
# command from CLI. See test documentation for more details.
 
INVALID_CLI_INVOCATIONS = [
 
    # init, invalid key specification
 
    ("gimmecert.cli.init", ["gimmecert", "init", "-k", "rsa"]),
 
    ("gimmecert.cli.init", ["gimmecert", "init", "-k", "rsa:not_a_number"]),
 
    ("gimmecert.cli.init", ["gimmecert", "init", "-k", "unsupported:algorithm"]),
 
]
 

	
 

	
 
@pytest.mark.parametrize("command_function, cli_invocation", INVALID_CLI_INVOCATIONS)
 
def test_invalid_parser_commands_and_options_produce_error(tmpdir, command_function, cli_invocation):
 
    """
 
    Tests handling of invalid CLI invocations by top-level and command
 
    parsers.
 

	
 
    This test helps greatly reduce duplication of code, at the expense
 
    of some flexibility.
 

	
 
    The passed-in command_function is mocked and set-up to return a
 
    success exit code, since the main point is to ensure the CLI
 
    supports specific commands and parameters. E.g. the parser should
 
    be the one producing errors.
 

	
 
    To add a new valid invocation of CLI, update the
 
    INVALID_CLI_INVOCATIONS variable above.
 
    """
 

	
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
    tmpdir.chdir()
 

	
 
    with mock.patch(command_function) as mock_command_function, mock.patch('sys.argv', cli_invocation):
 
        mock_command_function.return_value = gimmecert.commands.ExitCode.SUCCESS
 

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

	
 
        assert e_info.value.code == gimmecert.commands.ExitCode.ERROR_ARGUMENTS
 

	
 

	
 
@pytest.mark.parametrize("command", ["help", "init", "server", "client", "renew", "status"])
 
@pytest.mark.parametrize("help_option", ["--help", "-h"])
 
def test_command_exists_and_accepts_help_flag(tmpdir, command, help_option):
 
@@ -326,7 +376,8 @@ def test_command_exists_and_accepts_help_flag(tmpdir, command, help_option):
 

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

	
 
    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, tmpdir.basename, default_depth)
 
    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.patch('sys.argv', ['gimmecert', 'init', '-b', 'My Project'])
 
@mock.patch('gimmecert.cli.init')
 
def test_init_command_invoked_with_correct_parameters_with_options(mock_init, tmpdir):
 
@mock.patch('gimmecert.cli.KeyGenerator')
 
def test_init_command_invoked_with_correct_parameters_with_options(mock_key_generator, mock_init, tmpdir):
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
    tmpdir.chdir()
 
@@ -351,9 +406,11 @@ def test_init_command_invoked_with_correct_parameters_with_options(mock_init, tm
 

	
 
    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_init.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'My Project', default_depth, mock_key_generator.return_value)
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'server'])
tests/test_commands.py
Show inline comments
 
@@ -26,6 +26,7 @@ import sys
 
import cryptography.x509
 

	
 
import gimmecert.commands
 
import gimmecert.crypto
 

	
 
import pytest
 
from unittest import mock
 
@@ -37,7 +38,7 @@ def test_init_sets_up_directory_structure(tmpdir):
 
    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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    assert os.path.exists(base_dir.strpath)
 
    assert os.path.exists(ca_dir.strpath)
 
@@ -45,7 +46,7 @@ def test_init_sets_up_directory_structure(tmpdir):
 

	
 

	
 
def test_init_generates_single_ca_artifact_for_depth_1(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1)
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("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)
 
@@ -53,7 +54,7 @@ def test_init_generates_single_ca_artifact_for_depth_1(tmpdir):
 

	
 

	
 
def test_init_generates_three_ca_artifacts_for_depth_3(tmpdir):
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3)
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("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)
 
@@ -65,7 +66,7 @@ def test_init_generates_three_ca_artifacts_for_depth_3(tmpdir):
 

	
 

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

	
 
    level1_certificate = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    full_chain = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 
@@ -74,7 +75,7 @@ def test_init_outputs_full_chain_for_depth_1(tmpdir):
 

	
 

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

	
 
    level1_certificate = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    level2_certificate = tmpdir.join('.gimmecert', 'ca', 'level2.cert.pem').read()
 
@@ -87,26 +88,26 @@ def test_init_outputs_full_chain_for_depth_3(tmpdir):
 

	
 

	
 
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)
 
    status_code = gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("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)
 
    status_code = gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1)
 
    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"))
 

	
 
    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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("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()
 
@@ -249,29 +250,29 @@ 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.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

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

	
 
    assert stderr == ""
 
    assert "CA hierarchy initialised" in stdout
 
    assert "CA hierarchy initialised using 2048-bit RSA keys" in stdout
 
    assert ".gimmecert/ca/level1.cert.pem" in stdout
 
    assert ".gimmecert/ca/level1.key.pem" in stdout
 
    assert ".gimmecert/ca/chain-full.cert.pem" in stdout
 

	
 

	
 
def test_init_command_stdout_and_stderr_for_multiple_cas(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.commands.init(stdout_stream, stderr_stream, tmpdir.strpath, "myproject", 3, gimmecert.crypto.KeyGenerator("rsa:1024"))
 

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

	
 
    assert stderr == ""
 
    assert "CA hierarchy initialised" in stdout
 
    assert "CA hierarchy initialised using 1024-bit RSA keys" in stdout
 
    assert ".gimmecert/ca/level1.cert.pem" in stdout
 
    assert ".gimmecert/ca/level1.key.pem" in stdout
 
    assert ".gimmecert/ca/level2.cert.pem" in stdout
 
@@ -285,9 +286,9 @@ 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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, "myproject", 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

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

	
 
    stdout = stdout_stream.getvalue()
 
    stderr = stderr_stream.getvalue()
 
@@ -627,7 +628,7 @@ def test_status_reports_ca_hierarchy_information(tmpdir):
 
    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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    with freeze_time('2018-06-01 00:15:00'):
 
        status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath)
 
@@ -676,7 +677,7 @@ def test_status_reports_server_certificate_information(tmpdir):
 
    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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    with freeze_time('2018-02-01 00:15:00'):
 
        gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver1', None, None)
 
@@ -744,7 +745,7 @@ def test_status_reports_client_certificate_information(tmpdir):
 
    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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 3, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    with freeze_time('2018-02-01 00:15:00'):
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None)
 
@@ -802,7 +803,7 @@ def test_status_reports_no_server_certificates_were_issued(tmpdir):
 

	
 
    # 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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None)
 
        gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None)
 

	
 
@@ -823,7 +824,7 @@ def test_status_reports_no_client_certificates_were_issued(tmpdir):
 

	
 
    # 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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, gimmecert.crypto.KeyGenerator("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)
 

	
 
@@ -862,7 +863,7 @@ def test_certificate_marked_as_not_valid_or_expired_as_appropriate(tmpdir, subje
 

	
 
    # 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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, "My Project", 1, gimmecert.crypto.KeyGenerator("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)
 

	
tests/test_crypto.py
Show inline comments
 
@@ -27,6 +27,7 @@ from dateutil.relativedelta import relativedelta
 

	
 
import gimmecert.crypto
 

	
 
import pytest
 
from freezegun import freeze_time
 

	
 

	
 
@@ -107,7 +108,7 @@ 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)
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    assert isinstance(hierarchy, list)
 
    assert len(hierarchy) == depth
 
@@ -117,7 +118,7 @@ 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)
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    assert isinstance(hierarchy, list)
 
    assert len(hierarchy) == depth
 
@@ -127,7 +128,7 @@ 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)
 
    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)
 
@@ -137,8 +138,9 @@ def test_generate_ca_hierarchy_returns_list_of_private_key_certificate_pairs():
 
def test_generate_ca_hierarchy_subject_dns_have_correct_value():
 
    base_name = 'My Project'
 
    depth = 3
 
    key_generator = gimmecert.crypto.KeyGenerator("rsa:2048")
 

	
 
    level1, level2, level3 = [certificate for _, certificate in gimmecert.crypto.generate_ca_hierarchy(base_name, depth)]
 
    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'))
 
@@ -149,7 +151,7 @@ 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)
 
    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]
 
@@ -164,7 +166,7 @@ def test_generate_ca_hierarchy_private_keys_match_with_public_keys_in_certificat
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth)
 
    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]
 
@@ -179,7 +181,7 @@ def test_generate_ca_hierarchy_cas_have_differing_keys():
 
    base_name = 'My Project'
 
    depth = 3
 

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

	
 
    level1_private_key, _ = hierarchy[0]
 
    level2_private_key, _ = hierarchy[1]
 
@@ -198,7 +200,7 @@ def test_generate_ca_hierarchy_certificates_have_same_validity():
 
    base_name = 'My Project'
 
    depth = 3
 

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

	
 
    _, level1_certificate = hierarchy[0]
 
    _, level2_certificate = hierarchy[1]
 
@@ -248,7 +250,7 @@ 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)
 
    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)
 
@@ -261,7 +263,7 @@ def test_generate_ca_hierarchy_produces_certificates_with_ca_basic_constraints()
 

	
 

	
 
def test_issue_server_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -272,7 +274,7 @@ def test_issue_server_certificate_returns_certificate():
 

	
 

	
 
def test_issue_server_certificate_sets_correct_extensions():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -317,7 +319,7 @@ def test_issue_server_certificate_sets_correct_extensions():
 

	
 

	
 
def test_issue_server_certificate_has_correct_issuer_and_subject():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 4)
 
    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()
 
@@ -329,7 +331,7 @@ def test_issue_server_certificate_has_correct_issuer_and_subject():
 

	
 

	
 
def test_issue_server_certificate_has_correct_public_key():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -341,7 +343,7 @@ def test_issue_server_certificate_has_correct_public_key():
 

	
 
@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)
 
    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()
 
@@ -353,7 +355,7 @@ def test_issue_server_certificate_not_before_is_15_minutes_in_past():
 

	
 
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)
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
@@ -367,7 +369,7 @@ def test_issue_server_certificate_not_before_does_not_exceed_ca_validity():
 

	
 
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)
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
@@ -380,7 +382,7 @@ def test_issue_server_certificate_not_after_does_not_exceed_ca_validity():
 

	
 

	
 
def test_issue_server_certificate_incorporates_additional_dns_subject_alternative_names():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -401,7 +403,7 @@ def test_issue_server_certificate_incorporates_additional_dns_subject_alternativ
 

	
 

	
 
def test_issue_client_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -412,7 +414,7 @@ def test_issue_client_certificate_returns_certificate():
 

	
 

	
 
def test_issue_client_certificate_has_correct_issuer_and_subject():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 4)
 
    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()
 
@@ -424,7 +426,7 @@ def test_issue_client_certificate_has_correct_issuer_and_subject():
 

	
 

	
 
def test_issue_client_certificate_sets_correct_extensions():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -461,7 +463,7 @@ def test_issue_client_certificate_sets_correct_extensions():
 

	
 

	
 
def test_issue_client_certificate_has_correct_public_key():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -473,7 +475,7 @@ def test_issue_client_certificate_has_correct_public_key():
 

	
 
@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)
 
    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()
 
@@ -485,7 +487,7 @@ def test_issue_client_certificate_not_before_is_15_minutes_in_past():
 

	
 
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)
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
@@ -499,7 +501,7 @@ def test_issue_client_certificate_not_before_does_not_exceed_ca_validity():
 

	
 
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)
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
@@ -512,7 +514,7 @@ def test_issue_client_certificate_not_after_does_not_exceed_ca_validity():
 

	
 

	
 
def test_renew_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -524,7 +526,7 @@ def test_renew_certificate_returns_certificate():
 

	
 

	
 
def test_renew_certificate_has_correct_content():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1)
 
    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()
 
@@ -544,7 +546,7 @@ 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)
 
        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()
 
@@ -561,7 +563,7 @@ 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)
 
        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()
 
@@ -578,7 +580,7 @@ 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)
 
        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()
 
@@ -614,3 +616,75 @@ def test_generate_csr_returns_csr_with_passed_in_name():
 

	
 
    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",
 
])
 
def test_KeyGenerator_raises_exception_for_invalid_specification(key_specification):
 

	
 
    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)
 
    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")
 

	
 
    private_key_1 = key_generator_1()
 
    private_key_2 = key_generator_2()
 

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

	
 
    assert private_key_1.key_size == 1024
 
    assert private_key_2.key_size == 2048
 

	
 

	
 
@pytest.mark.parametrize("key_generator, expected_bit_size", [
 
    (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)
 

	
 
    for private_key, _ in hierarchy:
 
        assert private_key.key_size == expected_bit_size
tests/test_storage.py
Show inline comments
 
@@ -80,7 +80,7 @@ def test_write_certificate(tmpdir):
 

	
 
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)]
 
    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)
 
@@ -106,7 +106,7 @@ def test_is_initialised_returns_false_if_directory_is_not_initialised(tmpdir):
 

	
 
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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, 'My Project', 1, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

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

	
 
@@ -146,7 +146,7 @@ def test_read_certificate_returns_certificate(tmpdir):
 

	
 
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.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, 'My Project', 4, gimmecert.crypto.KeyGenerator("rsa:2048"))
 

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

	
tests/test_utils.py
Show inline comments
 
@@ -75,7 +75,7 @@ def test_date_range_to_str():
 

	
 

	
 
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)[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(
 
@@ -91,7 +91,7 @@ def test_get_dns_names_returns_empty_list_if_no_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)[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(
0 comments (0 inline, 0 general)