diff --git a/functional_tests/test_client.py b/functional_tests/test_client.py index c0cb3731384e7cb47e544c67cd5c4c2ac3d0fc15..a0d33260b21881967457a6678d76519175d6d281 100644 --- a/functional_tests/test_client.py +++ b/functional_tests/test_client.py @@ -44,7 +44,7 @@ def test_client_command_available_with_help(): assert exit_code == 0 assert stderr == "" assert stdout.startswith("usage: gimmecert client") - assert stdout.split('\n')[0].endswith(" entity_name") # First line of help. + assert stdout.split('\n')[2].endswith(" entity_name") # Third line of help. def test_client_command_requires_initialised_hierarchy(tmpdir): diff --git a/functional_tests/test_key_specification.py b/functional_tests/test_key_specification.py index 762d98b2a103941b9ad0aee9a8c21bb7e9f95013..1aeb7963be093f41bad1592f0b3d4022052d1748 100644 --- a/functional_tests/test_key_specification.py +++ b/functional_tests/test_key_specification.py @@ -142,3 +142,73 @@ def test_server_command_key_specification(tmpdir): # He nods with his head, observing that the generated private key # uses the same key size as he has specified. assert "Private-Key: (2048 bit)" in stdout + + +def test_client_command_key_specification(tmpdir): + # John is setting-up a quick and dirty project to test some + # functionality revolving around X.509 certificates. Since he does + # not care much about the strength of private keys for it, he + # wants to use 1024-bit RSA keys. + + # He switches to his project directory, and initialises the CA + # hierarchy, requesting that 1024-bit RSA keys should be used. + tmpdir.chdir() + run_command("gimmecert", "init", "--key-specification", "rsa:1024") + + # John issues a client certificate. + stdout, stderr, exit_code = run_command('gimmecert', 'client', 'myclient1') + + # John observes that the process was completed successfully. + assert exit_code == 0 + assert stderr == "" + + # He runs a command to see details about the generated private + # key. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/client/myclient1.key.pem') + + # And indeed, the generated private key uses the same size as the + # one he specified for the CA hierarchy. + assert "Private-Key: (1024 bit)" in stdout + + # He then has a look at the certificate. + stdout, _, _ = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/client/myclient1.cert.pem') + + # Likewise with the private key, the certificate is also using the + # 1024-bit RSA key. + assert "Public-Key: (1024 bit)" in stdout + + # At some point John realises that to cover all bases, he needs to + # have a test with a client that uses 2048-bit RSA keys as + # well. He does not want to regenerate all of the X.509 artefacts, + # and would like to instead issues a single 2048-bit RSA key for a + # specific client instead. + + # He starts off by having a look at the help for the client command. + stdout, stderr, exit_code = run_command("gimmecert", "client", "-h") + + # John notices the option for passing-in a key specification. + assert " --key-specification" in stdout + assert " -k" in stdout + + # John goes ahead and tries to issue a client certificate using + # key specification option. + stdout, stderr, exit_code = run_command("gimmecert", "client", "--key-specification", "rsas:2048", "myclient2") + + # Unfortunately, the command fails due to John's typo. + assert exit_code != 0 + assert "invalid key_specification" in stderr + + # John tries again, fixing his typo. + stdout, stderr, exit_code = run_command("gimmecert", "client", "--key-specification", "rsa:2048", "myclient2") + + # This time around he succeeds. + assert exit_code == 0 + assert stderr == "" + + # He runs a command to see details about the generated private + # key. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/client/myclient2.key.pem') + + # He nods with his head, observing that the generated private key + # uses the same key size as he has specified. + assert "Private-Key: (2048 bit)" in stdout diff --git a/gimmecert/cli.py b/gimmecert/cli.py index b31ca8c3d1c74efcdb9d5c3281f65a92065c9e0c..72df6e10e719be7c79abe3e0fbd4a39057109bbe 100644 --- a/gimmecert/cli.py +++ b/gimmecert/cli.py @@ -165,11 +165,14 @@ def setup_client_subcommand_parser(parser, subparsers): subparser.add_argument('entity_name', help='Name of the client entity.') subparser.add_argument('--csr', '-c', type=str, default=None, help='''Do not generate client private key locally, and use the passed-in \ certificate signing request (CSR) instead. Use dash (-) to read from standard input. Only the public key is taken from the CSR.''') + subparser.add_argument('--key-specification', '-k', type=key_specification, + help='''Specification/parameters to use for private key generation. \ + For RSA keys, use format rsa:BIT_LENGTH. Default is to use same algorithm/parameters as used by CA hierarchy.''', default=None) def client_wrapper(args): project_directory = os.getcwd() - return client(sys.stdout, sys.stderr, project_directory, args.entity_name, args.csr) + return client(sys.stdout, sys.stderr, project_directory, args.entity_name, args.csr, args.key_specification) subparser.set_defaults(func=client_wrapper) diff --git a/gimmecert/commands.py b/gimmecert/commands.py index 18a58d706b49329e0a48c8a5f8bc58f418fa434c..88b99d81f3cf3ca0b778c64fc401a2255fb6e0f2 100644 --- a/gimmecert/commands.py +++ b/gimmecert/commands.py @@ -138,8 +138,8 @@ def server(stdout, stderr, project_directory, entity_name, extra_dns_names, cust :param extra_dns_names: List of additional DNS names to include in the subject alternative name. :type extra_dns_names: list[str] - :param custom_csr_path: Path to custom certificate signing request to use for issuing client certificate. Set to None or "" to generate private key. - Do not use together with key_specification. + :param custom_csr_path: Path to custom certificate signing request to use for issuing server certificate. Set to None or "" to generate private key. + Always overrides passed-in key specification. :type custom_csr_path: str or None :param key_specification: Key specification to use when generating private keys for the server. Ignored if custom_csr_path is specified. Set to None to @@ -255,7 +255,7 @@ def usage(stdout, stderr, parser): return ExitCode.SUCCESS -def client(stdout, stderr, project_directory, entity_name, custom_csr_path): +def client(stdout, stderr, project_directory, entity_name, custom_csr_path, key_specification): """ Issues a client certificate using the CA hierarchy initialised within the specified directory. @@ -280,8 +280,13 @@ def client(stdout, stderr, project_directory, entity_name, custom_csr_path): :type entity_name: str :param custom_csr_path: Path to custom certificate signing request to use for issuing client certificate. Set to None or "" to generate private key. + Always overrides passed-in key specification. :type custom_csr_path: str or None + :param key_specification: Key specification to use when generating private keys for the client. Ignored if custom_csr_path is specified. Set to None to + default to issuing CA hiearchy algorithm and parameters. + :type key_specification: tuple(str, int) or None + :returns: Status code, one from gimmecert.commands.ExitCode. :rtype: int """ @@ -314,7 +319,10 @@ def client(stdout, stderr, project_directory, entity_name, custom_csr_path): csr = gimmecert.storage.read_csr(custom_csr_path) public_key = csr.public_key() else: - private_key = gimmecert.crypto.generate_private_key() + if not key_specification: + key_specification = gimmecert.crypto.key_specification_from_public_key(issuer_private_key.public_key()) + key_generator = gimmecert.crypto.KeyGenerator(key_specification[0], key_specification[1]) + private_key = key_generator() public_key = private_key.public_key() # Issue certificate using the passed-in information and diff --git a/tests/conftest.py b/tests/conftest.py index 61d9187d39b928642d4d149bf71b4d0c07550541..cf09ba0efef94564956c50abcc2f1d6854ddc4d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -155,11 +155,11 @@ def sample_project_directory(tmpdir): gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, entity_name, None, custom_csr_path, None) entity_name = "client-with-privkey-%d" % i - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, entity_name, None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, entity_name, None, None) entity_name = "client-with-csr-%d" % i custom_csr_path = custom_csr_dir.join("client-with-csr-%d.csr.pem" % i).strpath - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, entity_name, custom_csr_path) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, entity_name, custom_csr_path, None) return tmpdir diff --git a/tests/test_cli.py b/tests/test_cli.py index 3112aeef88c3e3f2c0ec3b21a6949fa5ba1bb70e..e2c58ea8036046ef3e9f476c14a2f4ab4162e337 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -253,6 +253,10 @@ VALID_CLI_INVOCATIONS = [ ("gimmecert.cli.client", ["gimmecert", "client", "--csr", "myclient.csr.pem", "myclient"]), ("gimmecert.cli.client", ["gimmecert", "client", "-c", "myclient.csr.pem", "myclient"]), + # client, key specification long and short option + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "rsa:4096", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa:1024", "myclient"]), + # renew, no options ("gimmecert.cli.renew", ["gimmecert", "renew", "server", "myserver"]), ("gimmecert.cli.renew", ["gimmecert", "renew", "client", "myclient"]), @@ -324,6 +328,11 @@ INVALID_CLI_INVOCATIONS = [ ("gimmecert.cli.server", ["gimmecert", "server", "-k", "rsa", "myserver"]), ("gimmecert.cli.server", ["gimmecert", "server", "-k", "rsa:not_a_number", "myserver"]), ("gimmecert.cli.server", ["gimmecert", "server", "-k", "unsupported:algorithm", "myserver"]), + + # client, invalid key specification + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa:not_a_number", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "unsupported:algorithm", "myclient"]), ] @@ -556,7 +565,7 @@ def test_client_command_invoked_with_correct_parameters(mock_client, tmpdir): gimmecert.cli.main() - mock_client.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'myclient', None) + mock_client.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'myclient', None, None) @mock.patch('sys.argv', ['gimmecert', 'renew']) @@ -737,3 +746,17 @@ def test_key_specification_returns_algorithm_and_parameters_for_valid_specificat algorithm, parameters = gimmecert.cli.key_specification(key_specification) # should not raise assert (algorithm, parameters) == expected_return_value + + +@mock.patch('sys.argv', ['gimmecert', 'client', '-k', 'rsa:1024', 'myclient']) +@mock.patch('gimmecert.cli.client') +def test_client_command_invoked_with_correct_parameters_with_key_specification(mock_client, tmpdir): + # This should ensure we don't accidentally create artifacts + # outside of test directory. + tmpdir.chdir() + + mock_client.return_value = gimmecert.commands.ExitCode.SUCCESS + + gimmecert.cli.main() + + mock_client.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'myclient', None, ('rsa', 1024)) diff --git a/tests/test_commands.py b/tests/test_commands.py index 7417bbc6f4c0b0436a62d32bef97874cbd90cdd1..d3f4e51565bc3d3ebfe1c34a50527fa310c18680 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -346,7 +346,7 @@ def test_client_reports_error_if_directory_is_not_initialised(tmpdir): stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.client(stdout_stream, stderr_stream, tmpdir.strpath, 'myclient', None) + status_code = gimmecert.commands.client(stdout_stream, stderr_stream, tmpdir.strpath, 'myclient', None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -357,7 +357,7 @@ def test_client_reports_error_if_directory_is_not_initialised(tmpdir): def test_client_returns_status_code(tmpdir): - status_code = gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', None) + status_code = gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', None, None) assert isinstance(status_code, int) @@ -366,7 +366,7 @@ def test_client_reports_success_and_paths_to_generated_artifacts(gctmpdir): stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', None) + status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -383,7 +383,7 @@ def test_client_outputs_private_key_to_file_without_csr(gctmpdir): private_key_file = gctmpdir.join('.gimmecert', 'client', 'myclient.key.pem') csr_file = gctmpdir.join('.gimmecert', 'client', 'myclient.csr.pem') - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) assert private_key_file.check(file=1) assert not csr_file.check() @@ -397,7 +397,7 @@ def test_client_outputs_private_key_to_file_without_csr(gctmpdir): def test_client_outputs_certificate_to_file(gctmpdir): certificate_file = gctmpdir.join('.gimmecert', 'client', 'myclient.cert.pem') - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) assert certificate_file.check(file=1) @@ -412,12 +412,12 @@ def test_client_errors_out_if_certificate_already_issued(gctmpdir): stderr_stream = io.StringIO() # Previous run. - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) existing_private_key = gctmpdir.join('.gimmecert', 'client', 'myclient.key.pem').read() certificate = gctmpdir.join('.gimmecert', 'client', 'myclient.cert.pem').read() # New run. - status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', None) + status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -506,7 +506,7 @@ def test_renew_reports_success_and_paths_to_client_artifacts(gctmpdir): stdout_stream = io.StringIO() stderr_stream = io.StringIO() - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'client', 'myclient', False, None, None) @@ -536,7 +536,7 @@ def test_renew_keeps_server_private_key(gctmpdir): def test_renew_keeps_client_private_key(gctmpdir): private_key_file = gctmpdir.join('.gimmecert', 'client', 'myclient.key.pem') - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) private_key_after_issuance = private_key_file.read() gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'client', 'myclient', False, None, None) @@ -562,7 +562,7 @@ def test_renew_replaces_server_certificate(gctmpdir): def test_renew_replaces_client_certificate(gctmpdir): certificate_file = gctmpdir.join('.gimmecert', 'client', 'myclient.cert.pem') - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) certificate_after_issuance = certificate_file.read() gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'client', 'myclient', False, None, None) @@ -748,13 +748,13 @@ def test_status_reports_client_certificate_information(tmpdir): 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) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None, None) with freeze_time('2018-03-01 00:15:00'): - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None, None) with freeze_time('2018-04-01 00:15:00'): - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient3', myclient3_csr_file.strpath) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient3', myclient3_csr_file.strpath, None) with freeze_time('2018-06-01 00:15:00'): status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath) @@ -804,8 +804,8 @@ 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, ("rsa", 2048)) - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None) - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient1', None, None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient2', None, None) status_code = gimmecert.commands.status(stdout_stream, stderr_stream, tmpdir.strpath) @@ -865,7 +865,7 @@ def test_certificate_marked_as_not_valid_or_expired_as_appropriate(tmpdir, subje with freeze_time(issuance_date): 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, None) - gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', None, None) # Move to specific date in future/past for different validity checks. with freeze_time(status_date): @@ -894,7 +894,7 @@ def test_client_reports_success_and_paths_to_generated_artifacts_with_csr(gctmpd custom_csr = gimmecert.crypto.generate_csr('blah', private_key) gimmecert.storage.write_csr(custom_csr, custom_csr_file.strpath) - status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', custom_csr_file.strpath) + status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', custom_csr_file.strpath, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -917,7 +917,7 @@ def test_client_outputs_passed_in_csr_to_file_without_private_key(gctmpdir): gimmecert.storage.write_csr(csr, custom_csr_file.strpath) custom_csr_file_content = custom_csr_file.read() - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', custom_csr_file.strpath) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', custom_csr_file.strpath, None) assert csr_file.check(file=1) assert not private_key_file.check() @@ -931,7 +931,7 @@ def test_client_uses_correct_public_key_without_csr(gctmpdir): private_key_file = gctmpdir.join('.gimmecert', 'client', 'myclient.key.pem') certificate_file = gctmpdir.join('.gimmecert', 'client', 'myclient.cert.pem') - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, None) private_key = gimmecert.storage.read_private_key(private_key_file.strpath) certificate = gimmecert.storage.read_certificate(certificate_file.strpath) @@ -950,7 +950,7 @@ def test_client_uses_correct_public_key_but_no_naming_with_csr(gctmpdir): csr = gimmecert.crypto.generate_csr('mycustomcsr', private_key) gimmecert.storage.write_csr(csr, custom_csr_file.strpath) - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', custom_csr_file.strpath) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', custom_csr_file.strpath, None) certificate = gimmecert.storage.read_certificate(certificate_file.strpath) @@ -1011,12 +1011,12 @@ def test_client_errors_out_if_certificate_already_issued_with_csr(gctmpdir): stderr_stream = io.StringIO() # Previous run. - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', custom_csr_file.strpath) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', custom_csr_file.strpath, None) existing_csr = gctmpdir.join('.gimmecert', 'client', 'myclient.csr.pem').read() certificate = gctmpdir.join('.gimmecert', 'client', 'myclient.cert.pem').read() # New run. - status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', custom_csr_file.strpath) + status_code = gimmecert.commands.client(stdout_stream, stderr_stream, gctmpdir.strpath, 'myclient', custom_csr_file.strpath, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -1093,7 +1093,7 @@ def test_renew_reports_success_and_paths_to_client_artifacts_with_csr(gctmpdir): csr = gimmecert.crypto.generate_csr("mytest", private_key) gimmecert.storage.write_csr(csr, csr_file.strpath) - gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', csr_file.strpath) + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', csr_file.strpath, None) status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'client', 'myclient', False, None, None) @@ -1354,7 +1354,7 @@ def test_client_reads_csr_from_stdin(mock_read_input, sample_project_directory, stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.client(stdout_stream, stderr_stream, sample_project_directory.strpath, entity_name, '-') + status_code = gimmecert.commands.client(stdout_stream, stderr_stream, sample_project_directory.strpath, entity_name, '-', None) assert status_code == 0 # Read stored/generated artefacts. @@ -1410,7 +1410,7 @@ def test_renew_client_reads_csr_from_stdin(mock_read_input, sample_project_direc certificate_file = sample_project_directory.join('.gimmecert', 'client', '%s.cert.pem' % entity_name) # Generate client certificate that will be renewed. - gimmecert.commands.client(io.StringIO(), io.StringIO(), sample_project_directory.strpath, entity_name, None) + gimmecert.commands.client(io.StringIO(), io.StringIO(), sample_project_directory.strpath, entity_name, None, None) # Mock our util for reading input from user. mock_read_input.return_value = key_with_csr.csr_pem @@ -1456,3 +1456,26 @@ def test_server_uses_passed_in_private_key_algorithm_and_parameters_when_generat private_key = gimmecert.storage.read_private_key(private_key_file.strpath) assert private_key.key_size == 1024 + + +def test_client_uses_same_private_key_algorithm_and_parameters_as_issuer_when_generating_private_key(tmpdir): + + private_key_file = tmpdir.join('.gimmecert', 'client', 'myclient.key.pem') + + gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 1024)) + gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', None, None) + + private_key = gimmecert.storage.read_private_key(private_key_file.strpath) + + assert private_key.key_size == 1024 + + +def test_client_uses_passed_in_private_key_algorithm_and_parameters_when_generating_private_key(gctmpdir): + + private_key_file = gctmpdir.join('.gimmecert', 'client', 'myclient.key.pem') + + gimmecert.commands.client(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myclient', None, ("rsa", 1024)) + + private_key = gimmecert.storage.read_private_key(private_key_file.strpath) + + assert private_key.key_size == 1024