Changeset - c3e3f7ebf69d
[Not reviewed]
0 5 0
Branko Majic (branko) - 4 years ago 2020-07-07 16:05:14
branko@majic.rs
GC-37: Added ECDSA support for issuing server certificates via server command:

- Added functional test.
- Added unit tests.
- Updated existing functional test that checks for avertising of curve
support for key specification in the init command to be a bit less
fragile in case the output gets broken-up into different lines in a
slightly different location.
- Implement ability to get public key specification out of ECDSA
public key.
- Expose ECDSA key specification in the server command.
- Updated inline documentation.
5 files changed with 113 insertions and 5 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_key_specification.py
Show inline comments
 
@@ -346,7 +346,7 @@ def test_initialisation_with_ecdsa_key_specification(tmpdir):
 
    assert "ecdsa:CURVE_NAME" in stdout
 

	
 
    # John can see a number of curves listed as supported.
 
    assert "Supported curves: " in stdout
 
    assert "curves: " in stdout
 
    assert "secp192r1" in stdout
 
    assert "secp224r1" in stdout
 
    assert "secp256k1" in stdout
 
@@ -390,3 +390,85 @@ def test_initialisation_with_ecdsa_key_specification(tmpdir):
 
    assert "Signature Algorithm: ecdsa-with-SHA256" in stdout
 
    assert "Public Key Algorithm: id-ecPublicKey" in stdout
 
    assert "ASN1 OID: prime256v1" in stdout
 

	
 

	
 
def test_server_command_default_key_specification_with_ecdsa(tmpdir):
 
    # John is setting-up a project to test some functionality
 
    # revolving around X.509 certificates. He has used RSA extensively
 
    # before, but now he wants to switch to using ECDSA private keys
 
    # instead.
 

	
 
    # He switches to his project directory, and initialises the CA
 
    # hierarchy, requesting that secp256r1 ECDSA keys should be used.
 
    tmpdir.chdir()
 
    run_command("gimmecert", "init", "--key-specification", "ecdsa:secp384r1")
 

	
 
    # John issues a server certificate.
 
    stdout, stderr, exit_code = run_command('gimmecert', 'server', 'myserver1')
 

	
 
    # 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', 'ec', '-noout', '-text', '-in', '.gimmecert/server/myserver1.key.pem')
 

	
 
    # And indeed, the generated private key uses the same algorithm as
 
    # the one he specified for the CA hierarchy.
 
    assert "ASN1 OID: secp384r1" in stdout
 

	
 

	
 
def test_server_command_key_specification_with_ecdsa(tmpdir):
 
    # John is setting-up a project where he needs to test performance
 
    # when using different ECDSA private key sizes.
 

	
 
    # He switches to his project directory, and initialises the CA
 
    # hierarchy, requesting that secp192r1 ECDSA keys should be used.
 
    tmpdir.chdir()
 
    run_command("gimmecert", "init", "--key-specification", "ecdsa:secp192r1")
 

	
 
    # Very soon he realizes that he needs to test performance using
 
    # different elliptic curve algorithms for proper comparison. He
 
    # starts off by having a look at the help for the server command
 
    # to see if there is an option that will satisfy his needs.
 
    stdout, stderr, exit_code = run_command("gimmecert", "server", "-h")
 

	
 
    # John notices the option for passing-in a key specification, and
 
    # that he can request ECDSA keys to be used with a specific curve.
 
    assert " --key-specification" in stdout
 
    assert " -k" in stdout
 
    assert "rsa:BIT_LENGTH" in stdout
 
    assert "ecdsa:CURVE_NAME" in stdout
 

	
 
    # John can see a number of curves listed as supported.
 
    assert "curves: " in stdout
 
    assert "secp192r1" in stdout
 
    assert "secp224r1" in stdout
 
    assert "secp256k1" in stdout
 
    assert "secp256r1" in stdout
 
    assert "secp384r1" in stdout
 
    assert "secp521r1" in stdout
 

	
 
    # John goes ahead and tries to issue a server certificate using
 
    # key specification option.
 
    stdout, stderr, exit_code = run_command("gimmecert", "server", "--key-specification", "ecdsa:secp224r11", "myserver1")
 

	
 
    # Unfortunately, the command fails due to John's typo.
 
    assert exit_code != 0
 
    assert "invalid key_specification" in stderr
 

	
 
    # John tries again, fixing his typo.
 
    stdout, stderr, exit_code = run_command("gimmecert", "server", "--key-specification", "ecdsa:secp224r1", "myserver1")
 

	
 
    # This time around he succeeds.
 
    assert exit_code == 0
 
    assert stderr == ""
 

	
 
    # He runs a command to see details about the generated private
 
    # key.
 
    stdout, _, _ = run_command('openssl', 'ec', '-noout', '-text', '-in', '.gimmecert/server/myserver1.key.pem')
 

	
 
    # He nods with his head, observing that the generated private key
 
    # uses the same algorithm as he has specified.
 
    assert "ASN1 OID: secp224r1" in stdout
gimmecert/cli.py
Show inline comments
 
@@ -164,7 +164,9 @@ def setup_server_subcommand_parser(parser, subparsers):
 
    certificate signing request (CSR) instead. Use dash (-) to read from standard input. 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 algorithm/parameters as used by CA hierarchy.''', default=None)
 
                           For RSA keys, use format rsa:BIT_LENGTH. For ECDSA keys, use format ecdsa:CURVE_NAME. \
 
                           Supported curves: secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1. \
 
                           Default is rsa:2048. Default is to use same algorithm/parameters as used by CA hierarchy.''', default=None)
 

	
 
    def server_wrapper(args):
 
        project_directory = os.getcwd()
gimmecert/crypto.py
Show inline comments
 
@@ -460,12 +460,14 @@ def key_specification_from_public_key(public_key):
 
    :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)
 
    :rtype: tuple(str, int or cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve)
 

	
 
    :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
 
    elif isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
 
        return "ecdsa", type(public_key.curve)
 

	
 
    raise ValueError("Unsupported public key instance passed-in: \"%s\" (%s)" % (str(public_key), type(public_key)))
tests/test_cli.py
Show inline comments
 
@@ -258,10 +258,24 @@ VALID_CLI_INVOCATIONS = [
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--csr", "myserver.csr.pem", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-c", "myserver.csr.pem", "myserver"]),
 

	
 
    # server, key specification long and short option
 
    # server, RSA key specification long and short option
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "rsa:4096", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "rsa:1024", "myserver"]),
 

	
 
    # server, ECDSA key specification long and short option
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "ecdsa:secp192r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:secp192r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "ecdsa:secp224r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:secp224r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "ecdsa:secp256k1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:secp256k1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "ecdsa:secp256r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:secp256r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "ecdsa:secp384r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:secp384r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--key-specification", "ecdsa:secp521r1", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:secp521r1", "myserver"]),
 

	
 
    # client, no options
 
    ("gimmecert.cli.client", ["gimmecert", "client", "myclient"]),
 

	
 
@@ -354,6 +368,7 @@ INVALID_CLI_INVOCATIONS = [
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "rsa", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "rsa:not_a_number", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "unsupported:algorithm", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-k", "ecdsa:unsupported_curve", "myserver"]),
 

	
 
    # client, invalid key specification
 
    ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa", "myclient"]),
tests/test_crypto.py
Show inline comments
 
@@ -21,6 +21,7 @@
 

	
 
import datetime
 

	
 
import cryptography.hazmat.primitives.asymmetric.ec
 
import cryptography.hazmat.primitives.asymmetric.rsa
 
import cryptography.x509
 
from dateutil.relativedelta import relativedelta
 
@@ -680,7 +681,13 @@ def test_generate_ca_hierarchy_uses_correct_rsa_bit_size(key_generator, expected
 

	
 
@pytest.mark.parametrize("specification", [
 
    ("rsa", 1024),
 
    ("rsa", 2048)
 
    ("rsa", 2048),
 
    ("ecdsa", cryptography.hazmat.primitives.asymmetric.ec.SECP192R1),
 
    ("ecdsa", cryptography.hazmat.primitives.asymmetric.ec.SECP224R1),
 
    ("ecdsa", cryptography.hazmat.primitives.asymmetric.ec.SECP256K1),
 
    ("ecdsa", cryptography.hazmat.primitives.asymmetric.ec.SECP256R1),
 
    ("ecdsa", cryptography.hazmat.primitives.asymmetric.ec.SECP384R1),
 
    ("ecdsa", cryptography.hazmat.primitives.asymmetric.ec.SECP521R1),
 
])
 
def test_key_specification_from_public_key_returns_correct_algorithm_and_parameters(specification):
 
    key_generator = gimmecert.crypto.KeyGenerator(specification[0], specification[1])
0 comments (0 inline, 0 general)