diff --git a/functional_tests/test_key_specification.py b/functional_tests/test_key_specification.py index a96aaec02ac76b2da81939c1116234b95309e4eb..48ce3602d0179f22b3c471d8b76da34d0d0c03fc 100644 --- a/functional_tests/test_key_specification.py +++ b/functional_tests/test_key_specification.py @@ -472,3 +472,85 @@ def test_server_command_key_specification_with_ecdsa(tmpdir): # 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 + + +def test_client_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:secp521r1") + + # John issues a client certificate. + stdout, stderr, exit_code = run_command('gimmecert', 'client', 'myclient1') + + # 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/client/myclient1.key.pem') + + # And indeed, the generated private key uses the same algorithm as + # the one he specified for the CA hierarchy. + assert "ASN1 OID: secp521r1" in stdout + + +def test_client_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 client command + # to see if there is an option that will satisfy his needs. + stdout, stderr, exit_code = run_command("gimmecert", "client", "-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 client certificate using + # key specification option. + stdout, stderr, exit_code = run_command("gimmecert", "client", "--key-specification", "ecdsa:secp224r11", "myclient1") + + # 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", "client", "--key-specification", "ecdsa:secp224r1", "myclient1") + + # 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/client/myclient1.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 diff --git a/gimmecert/cli.py b/gimmecert/cli.py index 3907a8c2619d34cdb0f9727139ddd29c3ada6bd4..49b8e78b5626095285ef2b32199e4bebc2f371ca 100644 --- a/gimmecert/cli.py +++ b/gimmecert/cli.py @@ -186,7 +186,9 @@ def setup_client_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 client_wrapper(args): project_directory = os.getcwd() diff --git a/tests/test_cli.py b/tests/test_cli.py index 7f04c15acc8fd7af831a9cefbfcd0e8597cc3794..4809a136bf84d4707a6e60121986b7cfc157941d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -283,10 +283,24 @@ VALID_CLI_INVOCATIONS = [ ("gimmecert.cli.client", ["gimmecert", "client", "--csr", "myclient.csr.pem", "myclient"]), ("gimmecert.cli.client", ["gimmecert", "client", "-c", "myclient.csr.pem", "myclient"]), - # client, key specification long and short option + # client, RSA key specification long and short option ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "rsa:4096", "myclient"]), ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa:1024", "myclient"]), + # client, ECDSA key specification long and short option + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "ecdsa:secp192r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "ecdsa:secp192r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "ecdsa:secp224r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "ecdsa:secp224r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "ecdsa:secp256k1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "ecdsa:secp256k1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "ecdsa:secp256r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "ecdsa:secp256r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "ecdsa:secp384r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "ecdsa:secp384r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "--key-specification", "ecdsa:secp521r1", "myclient"]), + ("gimmecert.cli.client", ["gimmecert", "client", "-k", "ecdsa:secp521r1", "myclient"]), + # renew, no options ("gimmecert.cli.renew", ["gimmecert", "renew", "server", "myserver"]), ("gimmecert.cli.renew", ["gimmecert", "renew", "client", "myclient"]), @@ -374,6 +388,7 @@ INVALID_CLI_INVOCATIONS = [ ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa", "myclient"]), ("gimmecert.cli.client", ["gimmecert", "client", "-k", "rsa:not_a_number", "myclient"]), ("gimmecert.cli.client", ["gimmecert", "client", "-k", "unsupported:algorithm", "myclient"]), + ("gimmecert.cli.server", ["gimmecert", "client", "-k", "ecdsa:unsupported_curve", "myserver"]), # renew, key specification without new private key option ("gimmecert.cli.renew", ["gimmecert", "renew", "-k", "rsa:1024", "server", "myserver"]),