Changeset - f3bde552fc94
[Not reviewed]
0 3 0
Branko Majic (branko) - 6 years ago 2018-04-12 21:39:53
branko@majic.rs
GC-22: Updated renew command to report correct artefact if CSR was originally used for issuing certificate:

- Added functional test for covering the scenario.
- Updated printout from the command to display path to CSR if CSR
artefact was used for initial (previous) certificate issuance.
- Updated existing unit tests and implemented new ones.
3 files changed with 163 insertions and 5 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_csr.py
Show inline comments
 
@@ -137,3 +137,77 @@ def test_server_certificate_issuance_by_passing_csr_as_file(tmpdir):
 

	
 
    # To his delight, they are identical.
 
    assert certificate_public_key == public_key
 

	
 

	
 
def test_renew_certificate_originally_issued_with_csr(tmpdir):
 
    # In one of his past projects, John has initialised CA hierarchy
 
    # and issued server and client certificate using CSR.
 
    tmpdir.chdir()
 

	
 
    run_command("openssl", "req", "-new", "-newkey", "rsa:2048", "-nodes", "-keyout", "myserver.key.pem",
 
                "-subj", "/CN=myserver", "-out", "mycustomserver.csr.pem")
 
    run_command("openssl", "req", "-new", "-newkey", "rsa:2048", "-nodes", "-keyout", "myclient.key.pem",
 
                "-subj", "/CN=myclient", "-out", "mycustomclient.csr.pem")
 

	
 
    run_command("gimmecert", "init")
 
    run_command("gimmecert", "server", "--csr", "mycustomserver.csr.pem", "myserver")
 
    run_command("gimmecert", "client", "--csr", "mycustomclient.csr.pem", "myclient")
 

	
 
    # After a while John comes back to the project and has a look at
 
    # generated artefacts.
 
    server_old_stored_csr = tmpdir.join(".gimmecert", "server", "myserver.csr.pem").read()
 
    server_old_certificate = tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read()
 

	
 
    client_old_stored_csr = tmpdir.join(".gimmecert", "client", "myclient.csr.pem").read()
 
    client_old_certificate = tmpdir.join(".gimmecert", "client", "myclient.cert.pem").read()
 

	
 
    # He decides to renew the certificates to update their
 
    # validity. First he runs renewal for the server certificate.
 
    stdout, stderr, exit_code = run_command("gimmecert", "renew", "server", "myserver")
 

	
 
    # No errors are shown, and John is informed about generated
 
    # artefacts. He notices that there is not mention of private key.
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "Renewed certificate for server myserver." in stdout
 
    assert ".gimmecert/server/myserver.csr.pem" in stdout
 
    assert ".gimmecert/server/myserver.cert.pem" in stdout
 
    assert ".gimmecert/server/myserver.key.pem" not in stdout
 

	
 
    # John has a look at generated artefacts.
 
    server_new_stored_csr = tmpdir.join(".gimmecert", "server", "myserver.csr.pem").read()
 
    server_new_certificate = tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read()
 
    server_csr_public_key, _, _ = run_command("openssl", "req", "-noout", "-pubkey", "-in", ".gimmecert/server/myserver.csr.pem")
 
    server_certificate_public_key, _, _ = run_command("openssl", "x509", "-noout", "-pubkey", "-in", ".gimmecert/server/myserver.cert.pem")
 

	
 
    # John notices that the reported CSR has remained unchanged, that
 
    # the certificate seems to have been updated, and that the public
 
    # key is the same in CSR and certificate.
 
    assert server_new_stored_csr == server_old_stored_csr
 
    assert server_new_certificate != server_old_certificate
 
    assert server_csr_public_key == server_certificate_public_key
 

	
 
    # John renews the client certificate.
 
    stdout, stderr, exit_code = run_command("gimmecert", "renew", "client", "myclient")
 

	
 
    # No errors are shown, and John is informed about generated
 
    # artefacts. He notices that there is not mention of private key.
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "Renewed certificate for client myclient." in stdout
 
    assert ".gimmecert/client/myclient.csr.pem" in stdout
 
    assert ".gimmecert/client/myclient.cert.pem" in stdout
 
    assert ".gimmecert/client/myclient.key.pem" not in stdout
 

	
 
    # John has a look at generated artefacts.
 
    client_new_stored_csr = tmpdir.join(".gimmecert", "client", "myclient.csr.pem").read()
 
    client_new_certificate = tmpdir.join(".gimmecert", "client", "myclient.cert.pem").read()
 
    client_csr_public_key, _, _ = run_command("openssl", "req", "-noout", "-pubkey", "-in", ".gimmecert/client/myclient.csr.pem")
 
    client_certificate_public_key, _, _ = run_command("openssl", "x509", "-noout", "-pubkey", "-in", ".gimmecert/client/myclient.cert.pem")
 

	
 
    # John notices that the reported CSR has remained unchanged, that
 
    # the certificate seems to have been updated, and that the public
 
    # key is the same in CSR and certificate.
 
    assert client_new_stored_csr == client_old_stored_csr
 
    assert client_new_certificate != client_old_certificate
 
    assert client_csr_public_key == client_certificate_public_key
gimmecert/commands.py
Show inline comments
 
@@ -367,24 +367,33 @@ def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_
 
    :rtype: int
 
    """
 

	
 
    # Set-up paths to possible artefacts.
 
    private_key_path = os.path.join(project_directory, '.gimmecert', entity_type, '%s.key.pem' % entity_name)
 
    csr_path = os.path.join(project_directory, '.gimmecert', entity_type, '%s.csr.pem' % entity_name)
 
    certificate_path = os.path.join(project_directory, '.gimmecert', entity_type, '%s.cert.pem' % entity_name)
 

	
 
    # Ensure the hierarchy has been previously initialised.
 
    if not gimmecert.storage.is_initialised(project_directory):
 
        print("No CA hierarchy has been initialised yet. Run the gimmecert init command and issue some certificates first.", file=stderr)
 

	
 
        return ExitCode.ERROR_NOT_INITIALISED
 

	
 
    # Ensure certificate has already been issued.
 
    if not os.path.exists(certificate_path):
 
        print("Cannot renew certificate. No existing certificate found for %s %s." % (entity_type, entity_name), file=stderr)
 

	
 
        return ExitCode.ERROR_UNKNOWN_ENTITY
 

	
 
    # Grab the signing CA private key and certificate.
 
    ca_hierarchy = gimmecert.storage.read_ca_hierarchy(os.path.join(project_directory, '.gimmecert', 'ca'))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[-1]
 

	
 
    # Information will be extracted from the old certificate.
 
    old_certificate = gimmecert.storage.read_certificate(certificate_path)
 

	
 
    # Generate new private key and use its public key for new
 
    # certificate. Otherwise just reuse existing public key in
 
    # certificate.
 
    if generate_new_private_key:
 
        private_key = gimmecert.crypto.generate_private_key()
 
        gimmecert.storage.write_private_key(private_key, private_key_path)
 
@@ -392,19 +401,36 @@ def renew(stdout, stderr, project_directory, entity_type, entity_name, generate_
 
    else:
 
        public_key = old_certificate.public_key()
 

	
 
    # Issue and write out the new certificate.
 
    certificate = gimmecert.crypto.renew_certificate(old_certificate, public_key, issuer_private_key, issuer_certificate)
 

	
 
    gimmecert.storage.write_certificate(certificate, certificate_path)
 

	
 
    # Type of artefacts reported depending on whether the private key
 
    # or CSR are present.
 
    if generate_new_private_key:
 
        print("Generated new private key and renewed certificate for %s %s." % (entity_type, entity_name), file=stdout)
 
    else:
 
        print("Renewed certificate for %s %s.\n" % (entity_type, entity_name), file=stdout)
 

	
 
    print("""{entity_type_titled} private key: .gimmecert/{entity_type}/{entity_name}.key.pem\n
 
    {entity_type_titled} certificate: .gimmecert/{entity_type}/{entity_name}.cert.pem""".format(entity_type_titled=entity_type.title(),
 
                                                                                                entity_type=entity_type,
 
                                                                                                entity_name=entity_name),
 
    # Output information about private key or CSR path.
 
    if os.path.exists(csr_path):
 
        print("{entity_type_titled} CSR: .gimmecert/{entity_type}/{entity_name}.csr.pem"
 
              .format(entity_type_titled=entity_type.title(),
 
                      entity_type=entity_type,
 
                      entity_name=entity_name),
 
              file=stdout)
 
    elif os.path.exists(private_key_path):
 
        print("{entity_type_titled} private key: .gimmecert/{entity_type}/{entity_name}.key.pem"
 
              .format(entity_type_titled=entity_type.title(),
 
                      entity_type=entity_type,
 
                      entity_name=entity_name),
 
              file=stdout)
 

	
 
    # Output information about generate certificate.
 
    print("{entity_type_titled} certificate: .gimmecert/{entity_type}/{entity_name}.cert.pem".
 
          format(entity_type_titled=entity_type.title(),
 
                 entity_type=entity_type,
 
                 entity_name=entity_name),
 
          file=stdout)
 

	
 
    return ExitCode.SUCCESS
tests/test_commands.py
Show inline comments
 
@@ -554,6 +554,7 @@ def test_renew_reports_success_and_paths_to_server_artifacts(tmpdir):
 
    assert "Renewed certificate for server myserver." 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
 
    assert stderr == ""
 

	
 

	
 
@@ -575,6 +576,7 @@ def test_renew_reports_success_and_paths_to_client_artifacts(tmpdir):
 
    assert "Renewed certificate for client myclient." 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
 
    assert stderr == ""
 

	
 

	
 
@@ -1219,3 +1221,59 @@ def test_server_errors_out_if_certificate_already_issued_with_csr(tmpdir):
 
    assert stdout == ""
 
    assert tmpdir.join('.gimmecert', 'server', 'myserver.csr.pem').read() == existing_csr
 
    assert tmpdir.join('.gimmecert', 'server', 'myserver.cert.pem').read() == certificate
 

	
 

	
 
def test_renew_reports_success_and_paths_to_server_artifacts_with_csr(tmpdir):
 
    depth = 1
 

	
 
    csr_file = tmpdir.join("mycustom.csr.pem")
 

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

	
 
    private_key = gimmecert.crypto.generate_private_key()
 
    csr = gimmecert.crypto.generate_csr("mytest", private_key)
 
    gimmecert.storage.write_csr(csr, csr_file.strpath)
 

	
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, depth)
 
    gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver', None, False, csr_file.strpath)
 

	
 
    status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, tmpdir.strpath, 'server', 'myserver', False)
 

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

	
 
    assert status_code == gimmecert.commands.ExitCode.SUCCESS
 
    assert "Renewed certificate for server myserver." in stdout
 
    assert ".gimmecert/server/myserver.csr.pem" in stdout
 
    assert ".gimmecert/server/myserver.cert.pem" in stdout
 
    assert ".gimmecert/server/myserver.key.pem" not in stdout
 
    assert stderr == ""
 

	
 

	
 
def test_renew_reports_success_and_paths_to_client_artifacts_with_csr(tmpdir):
 
    depth = 1
 

	
 
    csr_file = tmpdir.join("mycustom.csr.pem")
 

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

	
 
    private_key = gimmecert.crypto.generate_private_key()
 
    csr = gimmecert.crypto.generate_csr("mytest", private_key)
 
    gimmecert.storage.write_csr(csr, csr_file.strpath)
 

	
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, depth)
 
    gimmecert.commands.client(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myclient', csr_file.strpath)
 

	
 
    status_code = gimmecert.commands.renew(stdout_stream, stderr_stream, tmpdir.strpath, 'client', 'myclient', False)
 

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

	
 
    assert status_code == gimmecert.commands.ExitCode.SUCCESS
 
    assert "Renewed certificate for client myclient." in stdout
 
    assert ".gimmecert/client/myclient.csr.pem" in stdout
 
    assert ".gimmecert/client/myclient.cert.pem" in stdout
 
    assert ".gimmecert/client/myclient.key.pem" not in stdout
 
    assert stderr == ""
0 comments (0 inline, 0 general)