From dd7acf3d352f4375baf8b1dac402e87a142d11c4 2020-06-11 14:28:38 From: Branko Majic Date: 2020-06-11 14:28:38 Subject: [PATCH] GC-37: Added support for requesting custom RSA key size when renewing: - Added functional test. - Added unit tests. - Updated existing functional test for renew command help to cope with addition of one more option (output lines from help changed). - Added new CLI option for passing-in key specification, used in combination with the --new-private-key option. - Renew command function now accepts key specification parameter. Updated existing code and tests accordingly for the new function signature. - If key specification is not passed-in and new private key is requested, key size is extracted from existing artefacts (e.g. it does not use CA hierarchy's key size). --- diff --git a/functional_tests/test_key_specification.py b/functional_tests/test_key_specification.py index 1aeb7963be093f41bad1592f0b3d4022052d1748..43f143575ccba66791ebd4a40e593ee02d022f44 100644 --- a/functional_tests/test_key_specification.py +++ b/functional_tests/test_key_specification.py @@ -212,3 +212,111 @@ def test_client_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_renew_command_key_specification(tmpdir): + # John has set-up a project where he has issued a couple of + # certificates. + tmpdir.chdir() + run_command("gimmecert", "init") + + run_command('gimmecert', 'server', 'myserver1') + run_command('gimmecert', 'client', 'myclient1') + + # However, soon he realizes that he needs to perform some tests + # using a different RSA key size. John knows that Gimmecert comes + # with a renew command, so he has a quick look at its help. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "-h") + + # John notices the option for passing-in custom key specification. + assert " --key-specification" in stdout + assert " -k" in stdout + + # He goes ahead and tries to renew his server certificate. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "server", "-k", "rsa:1024", "myserver1") + + # However, Gimmecert informs him that the key specification option + # can only be used when requesting a new private key to be + # generated as well. + assert exit_code != 0 + assert "argument --key-specification/-k: must be used with --new-private-key/-p" in stderr + + # John goes ahead and adds that argument as well to his command. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "server", "-k", "rsa:1024", "-p", "myserver1") + + # This time everything goes without a hitch. + assert exit_code == 0 + assert stderr == "" + + # He checks the details about the generated private key, and + # disovers that Gimmecert generated the key according to his + # wishes. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/server/myserver1.key.pem') + assert "Private-Key: (1024 bit)" in stdout + + # John goes ahead and performs a similar operation for his client + # entity. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "client", "-k", "rsa:1024", "-p", "myclient1") + assert exit_code == 0 + assert stderr == "" + + # And once again, everything seems to check-out. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/client/myclient1.key.pem') + assert "Private-Key: (1024 bit)" in stdout + + # After some further testing, John decides to renew both of his + # certificates, together with generation of new private keys. He + # forgets to use the key specification option, though. Both + # commands succeed without errors. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "server", "-p", "myserver1") + assert exit_code == 0 + assert stderr == "" + + stdout, stderr, exit_code = run_command("gimmecert", "renew", "client", "-p", "myclient1") + assert exit_code == 0 + assert stderr == "" + + # John is unsure if the same key specification has been used, + # however. So he goes ahead and has a look at the server key. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/server/myserver1.key.pem') + + # And everything seems to be fine. + assert "Private-Key: (1024 bit)" in stdout + + # He performs the same check on the client key. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/client/myclient1.key.pem') + + # No problems here either. + assert "Private-Key: (1024 bit)" in stdout + + # Finally, John generates a couple of private keys directly on one + # of his managed machines, and issues certificates for them via + # CSRs. + run_command("openssl", "req", "-newkey", "rsa:3072", "-nodes", "-keyout", "myserver2.key.pem", + "-new", "-subj", "/CN=myserver2", "-out", "myserver2.csr.pem") + run_command("openssl", "req", "-newkey", "rsa:3072", "-nodes", "-keyout", "myclient2.key.pem", + "-new", "-subj", "/CN=myclient2", "-out", "myclient2.csr.pem") + run_command("gimmecert", "server", "--csr", "myserver2.csr.pem", "myserver2") + run_command("gimmecert", "client", "--csr", "myclient2.csr.pem", "myclient2") + + # After using his generated private keys for a while, John + # accidentally deletes them from his managed machine. Instead of + # redoing the whole process with CSRs, he decides to simply + # regenerate the private keys and certificates and copy them over. + run_command('gimmecert', 'renew', 'server', '--new-private-key', 'myserver2') + run_command('gimmecert', 'renew', 'client', '--new-private-key', 'myclient2') + + # John realizes that the original private keys he generated used + # 3072-bit RSA, while the CA hierarchy uses 2048-bit RSA. He + # decides to check if the generated key ended-up using CA-defaults + # or his own specification from before. + # + # He checks the server private key, and everything seems right - + # his own key specficiation from the old private key was used. + stdout, stderr, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/server/myserver2.key.pem') + assert "Private-Key: (3072 bit)" in stdout + + # Then he has a look at the client private key, and everything + # checks-out for it as well. + stdout, _, _ = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/client/myclient2.key.pem') + assert "Private-Key: (3072 bit)" in stdout diff --git a/functional_tests/test_renew.py b/functional_tests/test_renew.py index a3d3d4f59f1b771b87e73ff1c6c4b4ec9f7ebfd2..5eecd6293b7ff9911a3044634253e56b6d990435 100644 --- a/functional_tests/test_renew.py +++ b/functional_tests/test_renew.py @@ -46,7 +46,7 @@ def test_renew_command_available_with_help(): assert exit_code == 0 assert stderr == "" assert stdout.startswith("usage: gimmecert renew") - assert stdout.split('\n')[2].endswith("{server,client} entity_name") # Third line of help (first two are options) + assert stdout.split('\n')[3].endswith("{server,client} entity_name") # Fourth line of help (first two are options) def test_renew_command_requires_initialised_hierarchy(tmpdir): diff --git a/gimmecert/cli.py b/gimmecert/cli.py index 72df6e10e719be7c79abe3e0fbd4a39057109bbe..a0ccbfdaddc45d312fcb8fd8068dce524d435d00 100644 --- a/gimmecert/cli.py +++ b/gimmecert/cli.py @@ -209,10 +209,21 @@ def setup_renew_subcommand_parser(parser, subparsers): existing certificate, and use the passed-in certificate signing request (CSR) instead. Use dash (-) to read from standard input. \ If private key exists, it will be removed. Mutually exclusive with the --new-private-key option. 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 specification as used for current certificate.''', default=None) + def renew_wrapper(args): + # This is a workaround for having the key specification option + # be dependant on new private key option, since argparse + # cannot provide such verification on its own. + if args.key_specification and not args.new_private_key: + subparser.error("argument --key-specification/-k: must be used with --new-private-key/-p") + project_directory = os.getcwd() - return renew(sys.stdout, sys.stderr, project_directory, args.entity_type, args.entity_name, args.new_private_key, args.csr, args.dns_names) + return renew(sys.stdout, sys.stderr, project_directory, args.entity_type, args.entity_name, args.new_private_key, args.csr, args.dns_names, + args.key_specification) subparser.set_defaults(func=renew_wrapper) diff --git a/gimmecert/commands.py b/gimmecert/commands.py index 88b99d81f3cf3ca0b778c64fc401a2255fb6e0f2..9729012a8fe6d9af44e50146875b274f3c105595 100644 --- a/gimmecert/commands.py +++ b/gimmecert/commands.py @@ -350,7 +350,7 @@ def client(stdout, stderr, project_directory, entity_name, custom_csr_path, key_ return ExitCode.SUCCESS -def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_new_private_key, custom_csr_path, dns_names): +def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_new_private_key, custom_csr_path, dns_names, key_specification): """ Renews existing certificate, while optionally generating a new private key in the process. Naming and extensions are preserved. @@ -380,6 +380,10 @@ def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_ set the value to empty list. To keep the existing DNS names, set the value to None. Valid only for server certificates. :type dns_names: list[str] or None + :param key_specification: Key specification to use when generating new private key. Ignored if custom_csr_path is specified. Set to None to + default to same algorithm and parameters currently used for the entity. + :type key_specification: tuple(str, int) or None + :returns: Status code, one from gimmecert.commands.ExitCode. :rtype: int """ @@ -419,7 +423,14 @@ def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_ # certificate. Otherwise just reuse existing public key in # certificate. if generate_new_private_key: - private_key = gimmecert.crypto.generate_private_key() + + if key_specification: + key_generator = gimmecert.crypto.KeyGenerator(key_specification[0], key_specification[1]) + else: + key_size = old_certificate.public_key().key_size + key_generator = gimmecert.crypto.KeyGenerator('rsa', key_size) + + private_key = key_generator() gimmecert.storage.write_private_key(private_key, private_key_path) public_key = private_key.public_key() elif custom_csr_path == '-': diff --git a/tests/test_cli.py b/tests/test_cli.py index e2c58ea8036046ef3e9f476c14a2f4ab4162e337..12992016ef5609cc5c6399d281d8471e7e7b6dc8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -279,6 +279,14 @@ VALID_CLI_INVOCATIONS = [ ("gimmecert.cli.renew", ["gimmecert", "renew", "-c", "myserver.csr.pem", "server", "myserver"]), ("gimmecert.cli.renew", ["gimmecert", "renew", "-c", "myclient.csr.pem", "client", "myclient"]), + # renew, server, key specification long and short option (new private key short/long form tested already) + ("gimmecert.cli.renew", ["gimmecert", "renew", "-p", "--key-specification", "rsa:1024", "server", "myserver"]), + ("gimmecert.cli.renew", ["gimmecert", "renew", "-p", "-k", "rsa:1024", "server", "myserver"]), + + # renew, client, key specification long and short option (new private key short/long form tested already) + ("gimmecert.cli.renew", ["gimmecert", "renew", "-p", "--key-specification", "rsa:1024", "client", "myclient"]), + ("gimmecert.cli.renew", ["gimmecert", "renew", "-p", "-k", "rsa:1024", "client", "myclient"]), + # status, no options ("gimmecert.cli.status", ["gimmecert", "status"]), ] @@ -333,6 +341,10 @@ INVALID_CLI_INVOCATIONS = [ ("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"]), + + # renew, key specification without new private key option + ("gimmecert.cli.renew", ["gimmecert", "renew", "-k", "rsa:1024", "server", "myserver"]), + ("gimmecert.cli.renew", ["gimmecert", "renew", "-k", "rsa:1024", "client", "myclient"]), ] @@ -591,7 +603,7 @@ def test_renew_command_invoked_with_correct_parameters_for_server(mock_renew, tm gimmecert.cli.main() - mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', False, None, None) + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', False, None, None, None) @mock.patch('sys.argv', ['gimmecert', 'renew', 'client', 'myclient']) @@ -605,7 +617,7 @@ def test_renew_command_invoked_with_correct_parameters_for_client(mock_renew, tm gimmecert.cli.main() - mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', False, None, None) + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', False, None, None, None) @mock.patch('sys.argv', ['gimmecert', 'renew', '--new-private-key', 'server', 'myserver']) @@ -619,7 +631,7 @@ def test_renew_command_invoked_with_correct_parameters_for_server_with_new_priva gimmecert.cli.main() - mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', True, None, None) + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', True, None, None, None) @mock.patch('sys.argv', ['gimmecert', 'renew', '--new-private-key', 'client', 'myclient']) @@ -633,7 +645,7 @@ def test_renew_command_invoked_with_correct_parameters_for_client_with_new_priva gimmecert.cli.main() - mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', True, None, None) + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', True, None, None, None) @mock.patch('sys.argv', ['gimmecert', 'renew', '--csr', 'mycustom.csr.pem', 'server', 'myserver']) @@ -647,7 +659,7 @@ def test_renew_command_invoked_with_correct_parameters_for_server_with_csr_optio gimmecert.cli.main() - mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', False, 'mycustom.csr.pem', None) + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', False, 'mycustom.csr.pem', None, None) @mock.patch('sys.argv', ['gimmecert', 'renew', '--csr', 'mycustom.csr.pem', 'client', 'myclient']) @@ -661,7 +673,7 @@ def test_renew_command_invoked_with_correct_parameters_for_client_with_csr_optio gimmecert.cli.main() - mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', False, 'mycustom.csr.pem', None) + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', False, 'mycustom.csr.pem', None, None) @mock.patch('sys.argv', ['gimmecert', 'renew', '--update-dns-names', 'myservice1.example.com,myservice2.example.com', 'server', 'myserver']) @@ -677,7 +689,7 @@ def test_renew_command_invoked_with_correct_parameters_for_client_with_update_dn mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, - 'server', 'myserver', False, None, ['myservice1.example.com', 'myservice2.example.com']) + 'server', 'myserver', False, None, ['myservice1.example.com', 'myservice2.example.com'], None) @mock.patch('sys.argv', ['gimmecert', 'status']) @@ -760,3 +772,31 @@ def test_client_command_invoked_with_correct_parameters_with_key_specification(m gimmecert.cli.main() mock_client.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'myclient', None, ('rsa', 1024)) + + +@mock.patch('sys.argv', ['gimmecert', 'renew', '--new-private-key', '--key-specification', 'rsa:1024', 'server', 'myserver']) +@mock.patch('gimmecert.cli.renew') +def test_renew_command_invoked_with_correct_parameters_for_server_with_new_private_key_and_key_specification_options(mock_renew, tmpdir): + # This should ensure we don't accidentally create artifacts + # outside of test directory. + tmpdir.chdir() + + mock_renew.return_value = gimmecert.commands.ExitCode.SUCCESS + + gimmecert.cli.main() + + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'server', 'myserver', True, None, None, ('rsa', 1024)) + + +@mock.patch('sys.argv', ['gimmecert', 'renew', '--new-private-key', '--key-specification', 'rsa:1024', 'client', 'myclient']) +@mock.patch('gimmecert.cli.renew') +def test_renew_command_invoked_with_correct_parameters_for_client_with_new_private_key_and_key_specification_options(mock_renew, tmpdir): + # This should ensure we don't accidentally create artifacts + # outside of test directory. + tmpdir.chdir() + + mock_renew.return_value = gimmecert.commands.ExitCode.SUCCESS + + gimmecert.cli.main() + + mock_renew.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath, 'client', 'myclient', True, None, None, ('rsa', 1024)) diff --git a/tests/test_commands.py b/tests/test_commands.py index d3f4e51565bc3d3ebfe1c34a50527fa310c18680..52f3aa29fecd19c62d308a4b7a9289944e65fbf2 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -433,7 +433,7 @@ def test_client_errors_out_if_certificate_already_issued(gctmpdir): def test_renew_returns_status_code(tmpdir): tmpdir.chdir() - status_code = gimmecert.commands.renew(io.StringIO(), io.StringIO(), tmpdir.strpath, 'server', 'myserver', False, None, None) + status_code = gimmecert.commands.renew(io.StringIO(), io.StringIO(), tmpdir.strpath, 'server', 'myserver', False, None, None, None) assert isinstance(status_code, int) @@ -443,7 +443,7 @@ def test_renew_reports_error_if_directory_is_not_initialised(tmpdir): stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, tmpdir.strpath, 'server', 'myserver', False, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, tmpdir.strpath, 'server', 'myserver', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -457,7 +457,7 @@ def test_renew_reports_error_if_no_existing_server_certificate_is_present(gctmpd stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -472,7 +472,7 @@ def test_renew_reports_error_if_no_existing_client_certificate_is_present(gctmpd stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'client', 'myclient', False, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'client', 'myclient', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -489,7 +489,7 @@ def test_renew_reports_success_and_paths_to_server_artifacts(gctmpdir): gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -508,7 +508,7 @@ def test_renew_reports_success_and_paths_to_client_artifacts(gctmpdir): 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) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'client', 'myclient', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -527,7 +527,7 @@ def test_renew_keeps_server_private_key(gctmpdir): gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) private_key_after_issuance = private_key_file.read() - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, None, None) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, None, None, None) private_key_after_renewal = private_key_file.read() assert private_key_after_issuance == private_key_after_renewal @@ -539,7 +539,7 @@ def test_renew_keeps_client_private_key(gctmpdir): 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) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'client', 'myclient', False, None, None, None) private_key_after_renewal = private_key_file.read() assert private_key_after_issuance == private_key_after_renewal @@ -551,7 +551,7 @@ def test_renew_replaces_server_certificate(gctmpdir): gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) certificate_after_issuance = certificate_file.read() - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, None, None) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, None, None, None) certificate_after_renewal = certificate_file.read() assert certificate_after_issuance != certificate_after_renewal @@ -565,7 +565,7 @@ def test_renew_replaces_client_certificate(gctmpdir): 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) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'client', 'myclient', False, None, None, None) certificate_after_renewal = certificate_file.read() assert certificate_after_issuance != certificate_after_renewal @@ -579,7 +579,7 @@ def test_renew_reports_success_and_paths_to_server_artifacts_with_new_key(gctmpd gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', True, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', True, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -596,11 +596,14 @@ def test_renew_generates_new_private_key_if_requested(gctmpdir): gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) private_key_after_issuance = private_key_file.read() + private_key_size_after_issuance = gimmecert.storage.read_private_key(private_key_file.strpath).key_size - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None, None) private_key_after_renewal = private_key_file.read() + private_key_size_after_renewal = gimmecert.storage.read_private_key(private_key_file.strpath).key_size assert private_key_after_issuance != private_key_after_renewal + assert private_key_size_after_issuance == private_key_size_after_renewal def test_status_returns_status_code(tmpdir): @@ -1070,7 +1073,7 @@ def test_renew_reports_success_and_paths_to_server_artifacts_with_csr(gctmpdir): gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, csr_file.strpath, None) - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -1095,7 +1098,7 @@ def test_renew_reports_success_and_paths_to_client_artifacts_with_csr(gctmpdir): 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) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'client', 'myclient', False, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -1120,7 +1123,7 @@ def test_renew_reports_success_and_paths_to_server_artifacts_with_csr_when_repla gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, csr_file.strpath, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', False, csr_file.strpath, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -1150,7 +1153,7 @@ def test_renew_replaces_server_private_key_with_csr(gctmpdir): assert private_key_file.check(file=1) - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, custom_csr_file.strpath, None) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, custom_csr_file.strpath, None, None) assert csr_file.check(file=1) @@ -1174,7 +1177,7 @@ def test_renew_raises_exception_if_both_new_private_key_generation_and_csr_are_p gimmecert.storage.write_csr(custom_csr, custom_csr_file.strpath) with pytest.raises(gimmecert.commands.InvalidCommandInvocation) as e_info: - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, custom_csr_file.strpath, None) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, custom_csr_file.strpath, None, None) assert str(e_info.value) == "Only one of the following two parameters should be specified: generate_new_private_key, custom_csr_path." @@ -1184,7 +1187,7 @@ def test_renew_raises_exception_if_update_dns_names_is_used_for_client_certifica with pytest.raises(gimmecert.commands.InvalidCommandInvocation) as e_info: gimmecert.commands.renew(io.StringIO(), io.StringIO(), sample_project_directory.strpath, 'client', 'client-with-privkey-1', - False, None, ["myservice.example.com"]) + False, None, ["myservice.example.com"], None) assert str(e_info.value) == "Updating DNS subject alternative names can be done only for server certificates." @@ -1201,7 +1204,7 @@ def test_renew_reports_success_and_paths_to_server_artifacts_with_private_key_wh gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, custom_csr_file.strpath, None) - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', True, None, None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, gctmpdir.strpath, 'server', 'myserver', True, None, None, None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -1226,7 +1229,7 @@ def test_renew_reports_success_and_paths_to_artifacts_when_renewing_server_certi status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, sample_project_directory.strpath, 'server', entity_name, - False, None, ["myservice.example.com"]) + False, None, ["myservice.example.com"], None) stdout = stdout_stream.getvalue() stderr = stderr_stream.getvalue() @@ -1249,7 +1252,7 @@ def test_renew_replaces_dns_names(gctmpdir): gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', - False, None, ["myservice1.example.com", "myservice2.example.com"]) + False, None, ["myservice1.example.com", "myservice2.example.com"], None) new_certificate_pem = certificate_file.read() new_certificate = gimmecert.storage.read_certificate(certificate_file.strpath) @@ -1272,7 +1275,7 @@ def test_renew_removes_dns_names(gctmpdir): old_certificate = gimmecert.storage.read_certificate(certificate_file.strpath) old_subject_alt_name = old_certificate.extensions.get_extension_for_class(cryptography.x509.SubjectAlternativeName).value - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, None, []) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', False, None, [], None) new_certificate_pem = certificate_file.read() new_certificate = gimmecert.storage.read_certificate(certificate_file.strpath) @@ -1300,7 +1303,7 @@ def test_renew_replaces_server_csr_with_private_key(gctmpdir): assert csr_file.check(file=1) - gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None) + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None, None) assert private_key_file.check(file=1) @@ -1386,7 +1389,7 @@ def test_renew_server_reads_csr_from_stdin(mock_read_input, sample_project_direc stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, sample_project_directory.strpath, "server", entity_name, False, '-', None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, sample_project_directory.strpath, "server", entity_name, False, '-', None, None) assert status_code == 0 # Read stored/generated artefacts. @@ -1418,7 +1421,7 @@ def test_renew_client_reads_csr_from_stdin(mock_read_input, sample_project_direc stdout_stream = io.StringIO() stderr_stream = io.StringIO() - status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, sample_project_directory.strpath, "client", entity_name, False, '-', None) + status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, sample_project_directory.strpath, "client", entity_name, False, '-', None, None) assert status_code == 0 # Read stored/generated artefacts. @@ -1479,3 +1482,29 @@ def test_client_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_renew_generates_new_private_key_with_same_size_as_old_one(gctmpdir): + private_key_file = gctmpdir.join('.gimmecert', 'server', 'myserver.key.pem') + + gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, ('rsa', 1024)) + private_key_after_issuance = private_key_file.read() + + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None, None) + private_key_after_renewal = private_key_file.read() + private_key_size_after_renewal = gimmecert.storage.read_private_key(private_key_file.strpath).key_size + + assert private_key_after_issuance != private_key_after_renewal + assert private_key_size_after_renewal == 1024 + + +def test_renew_generates_new_private_key_with_different_size_if_requested(gctmpdir): + private_key_file = gctmpdir.join('.gimmecert', 'server', 'myserver.key.pem') + + # Should produce 2048-bit RSA key (default from hierarchy). + gimmecert.commands.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, None) + + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None, ("rsa", 1024)) + private_key_size_after_renewal = gimmecert.storage.read_private_key(private_key_file.strpath).key_size + + assert private_key_size_after_renewal == 1024