Changeset - a6c723849541
[Not reviewed]
0 3 0
Branko Majic (branko) - 4 years ago 2020-07-08 22:17:25
branko@majic.rs
GC-37: Implement renewals with new private keys for ECDSA when previous certificate was issued using CSR:

- Added functional test that covers the scenario.
- Parametrise the unit test used to verify that new key generation
follows the same key specification.
- Update test for checking if new private key gets generated to use
key specification instead of key size.
3 files changed with 126 insertions and 20 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_csr.py
Show inline comments
 
@@ -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
gimmecert/commands.py
Show inline comments
 
@@ -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()
tests/test_commands.py
Show inline comments
 
@@ -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
0 comments (0 inline, 0 general)