Changeset - d52b62b9a9db
[Not reviewed]
0 5 0
Branko Majic (branko) - 4 years ago 2020-06-07 22:18:20
branko@majic.rs
GC-37: Use CA hierarchy issuer's key size when generating RSA private keys.
5 files changed with 96 insertions and 5 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_server.py
Show inline comments
 
@@ -202,3 +202,37 @@ def test_server_command_does_not_overwrite_existing_artifacts(tmpdir):
 
    # unchanged.
 
    assert tmpdir.join(".gimmecert", "server", "myserver.key.pem").read() == private_key
 
    assert tmpdir.join(".gimmecert", "server", "myserver.cert.pem").read() == certificate
 

	
 

	
 
def test_server_command_uses_same_rsa_key_size_as_ca_hierarchy(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 server certificates.
 
    stdout, stderr, exit_code = run_command('gimmecert', 'server', 'myserver')
 

	
 
    # 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/server/myserver.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/server/myserver.cert.pem')
 

	
 
    # Likewise with the private key, the certificate is also using the
 
    # 1024-bit RSA key.
 
    assert "Public-Key: (1024 bit)" in stdout
gimmecert/commands.py
Show inline comments
 
@@ -160,6 +160,10 @@ def server(stdout, stderr, project_directory, entity_name, extra_dns_names, cust
 
        print("Refusing to overwrite existing data. Certificate has already been issued for server %s." % entity_name, file=stderr)
 
        return ExitCode.ERROR_CERTIFICATE_ALREADY_ISSUED
 

	
 
    # Grab the issuing 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]
 

	
 
    # Grab the private key or CSR, and extract public key.
 
    if custom_csr_path == "-":
 
        csr_pem = gimmecert.utils.read_input(sys.stdin, stderr, "Please enter the CSR")
 
@@ -171,14 +175,12 @@ def server(stdout, stderr, project_directory, entity_name, extra_dns_names, cust
 
        public_key = csr.public_key()
 
        private_key = None
 
    else:
 
        private_key = gimmecert.crypto.generate_private_key()
 
        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()
 
        csr = None
 

	
 
    # Grab the issuing 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]
 

	
 
    # Issue the certificate.
 
    certificate = gimmecert.crypto.issue_server_certificate(entity_name, public_key, issuer_private_key, issuer_certificate, extra_dns_names)
 

	
gimmecert/crypto.py
Show inline comments
 
@@ -432,3 +432,24 @@ def generate_csr(name, private_key):
 
    )
 

	
 
    return csr
 

	
 

	
 
def key_specification_from_public_key(public_key):
 
    """
 
    Derives key specification (algorithm and associated parameters)
 
    from the passed-in public key. Key specification can be used for
 
    generating the private keys via KeyGenerator instances.
 

	
 
    :param public_key: Public
 
    :type public_key: cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey
 

	
 
    :returns: Key algorithm and parameter(s) for generating same type of keys as the passed-in public key.
 
    :rtype: tuple(str, int)
 

	
 
    :raises ValueError: If algorithm/parameters could not be derived from the passed-in public key.
 
    """
 

	
 
    if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
 
        return "rsa", public_key.key_size
 

	
 
    raise ValueError("Unsupported public key instance passed-in: \"%s\" (%s)" % (str(public_key), type(public_key)))
tests/test_commands.py
Show inline comments
 
@@ -1433,3 +1433,15 @@ def test_renew_client_reads_csr_from_stdin(mock_read_input, sample_project_direc
 
    assert stored_csr_public_numbers == custom_csr_public_numbers
 
    assert certificate_public_numbers == custom_csr_public_numbers
 
    assert certificate.subject != key_with_csr.csr.subject
 

	
 

	
 
def test_server_uses_same_private_key_algorithm_and_parameters_as_issuer_when_generating_private_key(tmpdir):
 

	
 
    private_key_file = tmpdir.join('.gimmecert', 'server', 'myserver.key.pem')
 

	
 
    gimmecert.commands.init(io.StringIO(), io.StringIO(), tmpdir.strpath, tmpdir.basename, 1, ("rsa", 1024))
 
    gimmecert.commands.server(io.StringIO(), io.StringIO(), tmpdir.strpath, 'myserver', None, None)
 

	
 
    private_key = gimmecert.storage.read_private_key(private_key_file.strpath)
 

	
 
    assert private_key.key_size == 1024
tests/test_crypto.py
Show inline comments
 
@@ -652,3 +652,25 @@ def test_generate_ca_hierarchy_uses_correct_rsa_bit_size(key_generator, expected
 

	
 
    for private_key, _ in hierarchy:
 
        assert private_key.key_size == expected_bit_size
 

	
 

	
 
@pytest.mark.parametrize("specification", [
 
    ("rsa", 1024),
 
    ("rsa", 2048)
 
])
 
def test_key_specification_from_public_key_returns_correct_algorithm_and_parameters(specification):
 
    key_generator = gimmecert.crypto.KeyGenerator(specification[0], specification[1])
 
    public_key = key_generator().public_key()
 

	
 
    algorithm, parameters = gimmecert.crypto.key_specification_from_public_key(public_key)
 

	
 
    assert (algorithm, parameters) == specification
 

	
 

	
 
def test_key_specification_raises_exception_for_invalid_public_key():
 
    public_key = "not_a_public_key_instance"
 

	
 
    with pytest.raises(ValueError) as e_info:
 
        gimmecert.crypto.key_specification_from_public_key(public_key)
 

	
 
    assert str(e_info.value) == "Unsupported public key instance passed-in: \"not_a_public_key_instance\" (<class 'str'>)"
0 comments (0 inline, 0 general)