diff --git a/functional_tests/test_csr.py b/functional_tests/test_csr.py index 36107adda4c6a675c8a49d6657d9ed637b6e8886..a3700d1b00d2609342a0e020b0735b58d20920cc 100644 --- a/functional_tests/test_csr.py +++ b/functional_tests/test_csr.py @@ -920,3 +920,102 @@ def test_renew_certificate_originally_issued_with_private_key_using_csr_ecdsa(tm assert client_new_certificate != client_old_certificate assert client_stored_csr == client_csr assert client_new_certificate_public_key == client_csr_public_key + + +def test_renew_certificate_originally_issued_with_csr_using_private_key_ecdsa(tmpdir): + # John has an existing project where he has generated a server and + # client private key with corresponding CSR. + tmpdir.chdir() + + run_command("openssl", "ecparam", "-genkey", "-noout", "-out", "myserver.key.pem", "-name", "secp256r1") + run_command("openssl", "req", "-new", "-key", "myserver.key.pem", "-subj", "/CN=myserver", "-out", "mycustomserver.csr.pem") + + run_command("openssl", "ecparam", "-genkey", "-noout", "-out", "myclient.key.pem", "-name", "secp256r1") + run_command("openssl", "req", "-new", "-key", "myclient.key.pem", "-subj", "/CN=myserver", "-out", "mycustomclient.csr.pem") + + # He wants to grab some certificates for those, so he goes ahead + # and initialises the CA hierarchy. + tmpdir.chdir() + run_command("gimmecert", "init") + + # He proceeds to issue a server and client certificate using the + # CSRs. + run_command("gimmecert", "server", "--csr", "mycustomserver.csr.pem", "myserver") + run_command("gimmecert", "client", "--csr", "mycustomserver.csr.pem", "myclient") + + # John has a look at generated artefacts. + server_old_certificate = tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read() + server_old_certificate_public_key, _, _ = run_command("openssl", "x509", "-noout", "-pubkey", "-in", ".gimmecert/server/myserver.cert.pem") + + client_old_certificate = tmpdir.join(".gimmecert", "client", "myclient.cert.pem").read() + client_old_certificate_public_key, _, _ = run_command("openssl", "x509", "-noout", "-pubkey", "-in", ".gimmecert/client/myclient.cert.pem") + + # John accidentally removes the generated private keys. + tmpdir.join('myserver.key.pem').remove() + tmpdir.join('myclient.key.pem').remove() + + # He realises that the issued certificates are now useless to him, + # and decides to renew the certificates and let Gimmecert generate + # private keys for him. + + # He goes ahead and renews the server certificate first, + # requesting a new private key along the way. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "--new-private-key", "server", "myserver") + + # No errors are shown, and John is informed about generated + # artefacts, and that the CSR has been replaced with a generated + # private key. + assert exit_code == 0 + assert stderr == "" + assert "Generated new private key and renewed certificate for server myserver." in stdout + assert "CSR used for issuance of previous certificate has been removed, and a private key has been generated in its place." in stdout + assert ".gimmecert/server/myserver.key.pem" in stdout + assert ".gimmecert/server/myserver.cert.pem" in stdout + assert ".gimmecert/server/myserver.csr.pem" not in stdout + + # John has a look at generated artefacts. + server_generated_private_key_public_key, _, _ = run_command("openssl", "ec", "-pubout", "-in", ".gimmecert/server/myserver.key.pem") + + server_new_certificate = tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read() + server_new_certificate_public_key, _, _ = run_command("openssl", "x509", "-noout", "-pubkey", "-in", ".gimmecert/server/myserver.cert.pem") + + # John notices that, for start, the CSR has indeed been removed + # from the filesystem, that the content of the certificate has + # changed, that the old public key is not the same as the new one, + # and that public key from the certificate matches with the + # private key. + assert not tmpdir.join(".gimmecert", "server", "myserver.csr.pem").check() + assert server_new_certificate != server_old_certificate + assert server_old_certificate_public_key != server_generated_private_key_public_key + assert server_new_certificate_public_key == server_generated_private_key_public_key + + # He goes ahead and renews the client certificate first, + # requesting a new private key along the way. + stdout, stderr, exit_code = run_command("gimmecert", "renew", "--new-private-key", "client", "myclient") + + # No errors are shown, and John is informed about generated + # artefacts, and that the CSR has been replaced with a generated + # private key. + assert exit_code == 0 + assert stderr == "" + assert "Generated new private key and renewed certificate for client myclient." in stdout + assert "CSR used for issuance of previous certificate has been removed, and a private key has been generated in its place." in stdout + assert ".gimmecert/client/myclient.key.pem" in stdout + assert ".gimmecert/client/myclient.cert.pem" in stdout + assert ".gimmecert/client/myclient.csr.pem" not in stdout + + # John has a look at generated artefacts. + client_generated_private_key_public_key, _, _ = run_command("openssl", "ec", "-pubout", "-in", ".gimmecert/client/myclient.key.pem") + + client_new_certificate = tmpdir.join(".gimmecert", "client", "myclient.cert.pem").read() + client_new_certificate_public_key, _, _ = run_command("openssl", "x509", "-noout", "-pubkey", "-in", ".gimmecert/client/myclient.cert.pem") + + # John notices that, for start, the CSR has indeed been removed + # from the filesystem, that the content of the certificate has + # changed, that the old public key is not the same as the new one, + # and that public key from the certificate matches with the + # private key. + assert not tmpdir.join(".gimmecert", "client", "myclient.csr.pem").check() + assert client_new_certificate != client_old_certificate + assert client_old_certificate_public_key != client_generated_private_key_public_key + assert client_new_certificate_public_key == client_generated_private_key_public_key diff --git a/gimmecert/commands.py b/gimmecert/commands.py index aaa7b77e270e7aaa77acfd6821167375b3b23ad7..9770551493651c2aef359fa9874f47631f334a61 100644 --- a/gimmecert/commands.py +++ b/gimmecert/commands.py @@ -424,12 +424,11 @@ def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_ # certificate. if generate_new_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) + # Use key specification identical to the old key. + if not key_specification: + key_specification = gimmecert.crypto.key_specification_from_public_key(old_certificate.public_key()) + key_generator = gimmecert.crypto.KeyGenerator(key_specification[0], key_specification[1]) private_key = key_generator() gimmecert.storage.write_private_key(private_key, private_key_path) public_key = private_key.public_key() diff --git a/tests/test_commands.py b/tests/test_commands.py index 9239a67fea57254ed55ff44d58ee0516101d347d..78f24aff469d0647da8f225ba69e1806d86bfb26 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -596,14 +596,16 @@ 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 + public_key_after_issuance = gimmecert.storage.read_private_key(private_key_file.strpath).public_key() + key_specification_after_issuance = gimmecert.crypto.key_specification_from_public_key(public_key_after_issuance) 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 + public_key_after_renewal = gimmecert.storage.read_private_key(private_key_file.strpath).public_key() + key_specification_after_renewal = gimmecert.crypto.key_specification_from_public_key(public_key_after_renewal) assert private_key_after_issuance != private_key_after_renewal - assert private_key_size_after_issuance == private_key_size_after_renewal + assert key_specification_after_issuance == key_specification_after_renewal def test_status_returns_status_code(tmpdir): @@ -1501,27 +1503,33 @@ def test_client_uses_passed_in_private_key_algorithm_and_parameters_when_generat assert private_key.key_size == 1024 -def test_renew_generates_new_private_key_with_same_size_as_old_one(gctmpdir): +def test_renew_generates_new_private_key_with_different_size_if_requested(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() + # 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, None) - private_key_after_renewal = private_key_file.read() + 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_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): +@pytest.mark.parametrize("key_specification", [ + ('rsa', 1024), + ('ecdsa', cryptography.hazmat.primitives.asymmetric.ec.SECP256K1), +]) +def test_renew_generates_new_private_key_with_same_key_specification_as_old_one(gctmpdir, key_specification): 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.server(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'myserver', None, None, key_specification) + private_key_after_issuance = private_key_file.read() - 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 + gimmecert.commands.renew(io.StringIO(), io.StringIO(), gctmpdir.strpath, 'server', 'myserver', True, None, None, None) + private_key_after_renewal = private_key_file.read() - assert private_key_size_after_renewal == 1024 + public_key_after_renewal = gimmecert.storage.read_private_key(private_key_file.strpath).public_key() + key_specification_after_renewal = gimmecert.crypto.key_specification_from_public_key(public_key_after_renewal) + + assert private_key_after_issuance != private_key_after_renewal + assert key_specification_after_renewal == key_specification