Changeset - 3df373672d38
[Not reviewed]
0 4 0
Branko Majic (branko) - 6 years ago 2018-04-27 22:10:56
branko@majic.rs
GC-22: Updated client command to allow reading of CSR from stdin:

- Implemented functional test covering reading of CSR from stdin for
the client command.
- Updated client command CLI help.
- Updated client command to read CSR from stdin if passed-in path is
set to '-'.
- Implemented relevant unit tests.
4 files changed with 96 insertions and 2 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_csr.py
Show inline comments
 
@@ -473,3 +473,64 @@ def test_server_command_accepts_csr_from_stdin(tmpdir):
 

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

	
 

	
 
def test_client_command_accepts_csr_from_stdin(tmpdir):
 
    # John is working on a project where he has already generated
 
    # client private key.
 
    tmpdir.chdir()
 
    run_command("openssl", "genrsa", "-out", "myclient1.key.pem", "2048")
 

	
 
    # However, he still needs to have a CA as a trustpoint, so he goes
 
    # ahead and initialises Gimmecert for this purpose.
 
    run_command("gimmecert", "init")
 

	
 
    # Before issuing the certificate, he generates a CSR for the
 
    # client private key.
 
    custom_csr, _, exit_code = run_command("openssl", "req", "-new", "-key", "myclient1.key.pem", "-subj", "/CN=myclient1")
 

	
 
    # John realises that although the CSR generation was successful, he
 
    # forgot to output it to a file.
 
    assert exit_code == 0
 
    assert "BEGIN CERTIFICATE REQUEST" in custom_csr
 
    assert "END CERTIFICATE REQUEST" in custom_csr
 

	
 
    # He could output the CSR into a file, and feed that into
 
    # Gimmecert, but he feels a bit lazy. Instead, John tries to pass
 
    # in a dash ("-") as input, knowing that it is commonly used as
 
    # shorthand for reading from standard input.
 
    prompt_failure, output, exit_code = run_interactive_command([], "gimmecert", "client", "--csr", "-", "myclient1")
 

	
 
    # John sees that the application has prompted him to provide the
 
    # CSR interactively, and that it waits for his input.
 
    assert exit_code is None, "Output was: %s" % output
 
    assert prompt_failure == "Command got stuck waiting for input.", "Output was: %s" % output
 
    assert "Please enter the CSR (finish with Ctrl-D on an empty line):" in output
 

	
 
    # John reruns the command, this time passing-in the CSR and ending
 
    # the input with Ctrl-D.
 
    prompt_failure, output, exit_code = run_interactive_command([('Please enter the CSR \(finish with Ctrl-D on an empty line\):', custom_csr + '\n\004')],
 
                                                                "gimmecert", "client", "--csr", "-", "myclient1")
 

	
 
    # The operation is successful, and he is presented with
 
    # information about generated artefacts.
 
    assert prompt_failure is None
 
    assert exit_code == 0
 
    assert ".gimmecert/client/myclient1.cert.pem" in output
 
    assert ".gimmecert/client/myclient1.csr.pem" in output
 

	
 
    # John also notices that there is no mention of a private key.
 
    assert ".gimmecert/client/myclient1.key.pem" not in output
 

	
 
    # John notices that the content of stored CSR is identical to the
 
    # one he provided.
 
    stored_csr = tmpdir.join(".gimmecert", "client", "myclient1.csr.pem").read()
 
    assert custom_csr == stored_csr
 

	
 
    # John then quickly has a look at the public key associated with
 
    # the private key, and public key stored in certificate.
 
    public_key, _, _ = run_command("openssl", "rsa", "-pubout", "-in", "myclient1.key.pem")
 
    certificate_public_key, _, _ = run_command("openssl", "x509", "-pubkey", "-noout", "-in", ".gimmecert/client/myclient1.cert.pem")
 

	
 
    # To his delight, they are identical.
 
    assert certificate_public_key == public_key
gimmecert/cli.py
Show inline comments
 
@@ -126,7 +126,7 @@ def setup_client_subcommand_parser(parser, subparsers):
 
    subparser = subparsers.add_parser('client', description='Issue client certificate.')
 
    subparser.add_argument('entity_name', help='Name of the client entity.')
 
    subparser.add_argument('--csr', '-c', type=str, default=None, help='''Do not generate client private key locally, and use the passed-in \
 
    certificate signing request (CSR) instead.''')
 
    certificate signing request (CSR) instead. Use dash (-) to read from standard input.''')
 

	
 
    def client_wrapper(args):
 
        project_directory = os.getcwd()
gimmecert/commands.py
Show inline comments
 
@@ -323,7 +323,11 @@ def client(stdout, stderr, project_directory, entity_name, custom_csr_path):
 
    issuer_private_key, issuer_certificate = ca_hierarchy[-1]
 

	
 
    # Either read public key from CSR, or generate a new private key.
 
    if custom_csr_path:
 
    if custom_csr_path == "-":
 
        csr_pem = gimmecert.utils.read_input(sys.stdin, stderr, "Please enter the CSR")
 
        csr = gimmecert.utils.csr_from_pem(csr_pem)
 
        public_key = csr.public_key()
 
    elif custom_csr_path:
 
        csr = gimmecert.storage.read_csr(custom_csr_path)
 
        public_key = csr.public_key()
 
    else:
tests/test_commands.py
Show inline comments
 
@@ -1498,3 +1498,32 @@ def test_server_reads_csr_from_stdin(mock_read_input, sample_project_directory,
 
    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
 

	
 

	
 
@mock.patch('gimmecert.utils.read_input')
 
def test_client_reads_csr_from_stdin(mock_read_input, sample_project_directory, key_with_csr):
 
    entity_name = 'myclient'
 
    stored_csr_file = sample_project_directory.join('.gimmecert', 'client', '%s.csr.pem' % entity_name)
 
    certificate_file = sample_project_directory.join('.gimmecert', 'client', '%s.cert.pem' % entity_name)
 

	
 
    # Mock our util for reading input from user.
 
    mock_read_input.return_value = key_with_csr.csr_pem
 

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

	
 
    status_code = gimmecert.commands.client(stdout_stream, stderr_stream, sample_project_directory.strpath, entity_name, '-')
 
    assert status_code == 0
 

	
 
    # Read stored/generated artefacts.
 
    stored_csr = gimmecert.storage.read_csr(stored_csr_file.strpath)
 
    certificate = gimmecert.storage.read_certificate(certificate_file.strpath)
 

	
 
    custom_csr_public_numbers = key_with_csr.csr.public_key().public_numbers()
 
    stored_csr_public_numbers = stored_csr.public_key().public_numbers()
 
    certificate_public_numbers = certificate.public_key().public_numbers()
 

	
 
    mock_read_input.assert_called_once_with(sys.stdin, stderr_stream, "Please enter the CSR")
 
    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
0 comments (0 inline, 0 general)