Changeset - 11712c273c16
[Not reviewed]
0 7 0
Branko Majic (branko) - 6 years ago 2018-03-01 14:24:54
branko@majic.rs
GC-3: Implement option for specifying CA hierarchy depth during initialisation:

- Minor fix for option checks to make the more reliable (for short
flags).
- Added functional test for initialising a CA hierarchy with custom
depth.
- Added option for specifying the CA hierarchy depth (defaults to 1).
- Updated the init command to accept and process the CA hierarchy
depth option correctly.
- Updated function used for issuing certificates to accept list of
extensions to be added to certificate.
- Updated function used for generating the CA hierarchy to add the CA
basic constraint extension.
7 files changed with 304 insertions and 31 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_init.py
Show inline comments
 
@@ -136,7 +136,7 @@ def test_initialisation_with_custom_base_name(tmpdir):
 
    # draws his attention. The option seems to be usable for
 
    # specifying the base name for the CAs - exactly what he needed.
 
    assert "--ca-base-name" in stdout
 
    assert "-b" in stdout
 
    assert " -b " in stdout
 

	
 
    # John switches to his project directory.
 
    tmpdir.chdir()
 
@@ -165,3 +165,122 @@ def test_initialisation_with_custom_base_name(tmpdir):
 
    # project name.
 
    assert issuer_dn.rstrip() == subject_dn.rstrip() == "CN = My Project Level 1"
 
    assert tmpdir.basename not in issuer_dn
 

	
 

	
 
def test_initialisation_with_custom_hierarchy_depth(tmpdir):
 
    # John is now involved in a project where the CA hierarchy used
 
    # for issuing certificates is supposed to be deeper than 1. In
 
    # other words, he needs Level 1 CA -> Level 2 CA -> Level 3 CA ->
 
    # end entity certificates.
 

	
 
    # He hopes that the Gimmecert tool can still help him with this
 
    # scenario as well. At first, he runs the tool with a help flag.
 
    stdout, _, _ = run_command('gimmecert', 'init', '-h')
 

	
 
    # John notices there is an option to specify hierarchy depth.
 
    assert "--ca-hierarchy-depth" in stdout
 
    assert " -d " in stdout
 

	
 
    # John switches to his project directory.
 
    tmpdir.chdir()
 

	
 
    # He runs the command, specifying this time around the desired CA
 
    # hierarchy depth.
 
    stdout, stderr, exit_code = run_command('gimmecert', 'init', '--ca-hierarchy-depth', '3')
 

	
 
    # Command finishes execution with success, and he is informed that
 
    # his CA hierarchy has been initialised. He notices there are many
 
    # more CA artifacts listed now.
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert "CA hierarchy initialised." in stdout
 
    assert ".gimmecert/ca/level1.key.pem" in stdout
 
    assert ".gimmecert/ca/level1.cert.pem" in stdout
 
    assert ".gimmecert/ca/level2.key.pem" in stdout
 
    assert ".gimmecert/ca/level2.cert.pem" in stdout
 
    assert ".gimmecert/ca/level3.key.pem" in stdout
 
    assert ".gimmecert/ca/level3.cert.pem" in stdout
 
    assert ".gimmecert/ca/chain-full.cert.pem" in stdout
 

	
 
    # John goes ahead and inspects the CA keys first.
 
    stdout1, stderr1, exit_code1 = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/ca/level1.key.pem')
 
    stdout2, stderr2, exit_code2 = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/ca/level2.key.pem')
 
    stdout3, stderr3, exit_code3 = run_command('openssl', 'rsa', '-noout', '-text', '-in', '.gimmecert/ca/level3.key.pem')
 

	
 
    assert exit_code1 == 0
 
    assert stderr1 == ""
 
    assert "Private-Key: (2048 bit)" in stdout1
 

	
 
    assert exit_code2 == 0
 
    assert stderr2 == ""
 
    assert "Private-Key: (2048 bit)" in stdout2
 

	
 
    assert exit_code3 == 0
 
    assert stderr3 == ""
 
    assert "Private-Key: (2048 bit)" in stdout3
 

	
 
    # John then has a look at the generated CA certificate files.
 
    stdout1, stderr1, exit_code1 = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/ca/level1.cert.pem')
 
    stdout2, stderr2, exit_code2 = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/ca/level2.cert.pem')
 
    stdout3, stderr3, exit_code3 = run_command('openssl', 'x509', '-noout', '-text', '-in', '.gimmecert/ca/level3.cert.pem')
 

	
 
    # John observes that there are no errors, and that certificate
 
    # details are being shown.
 
    assert 'Certificate:' in stdout1
 
    assert 'Certificate:' in stdout2
 
    assert 'Certificate:' in stdout3
 

	
 
    # John then runs a bunch of commands to get the subject and issuer
 
    # DNs of certificates.
 
    issuer_dn1, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/ca/level1.cert.pem')
 
    subject_dn1, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/ca/level1.cert.pem')
 
    issuer_dn1 = issuer_dn1.replace('issuer=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1)  # OpenSSL 1.0 vs 1.1 formatting
 
    subject_dn1 = subject_dn1.replace('subject=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1)  # OpenSSL 1.0 vs 1.1 formatting
 

	
 
    issuer_dn2, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/ca/level2.cert.pem')
 
    subject_dn2, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/ca/level2.cert.pem')
 
    issuer_dn2 = issuer_dn2.replace('issuer=', '', 2).rstrip().replace(' /CN=', 'CN = ', 2)  # OpenSSL 1.0 vs 1.2 formatting
 
    subject_dn2 = subject_dn2.replace('subject=', '', 2).rstrip().replace(' /CN=', 'CN = ', 2)  # OpenSSL 1.0 vs 1.2 formatting
 

	
 
    issuer_dn3, _, _ = run_command('openssl', 'x509', '-noout', '-issuer', '-in', '.gimmecert/ca/level3.cert.pem')
 
    subject_dn3, _, _ = run_command('openssl', 'x509', '-noout', '-subject', '-in', '.gimmecert/ca/level3.cert.pem')
 
    issuer_dn3 = issuer_dn3.replace('issuer=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1)  # OpenSSL 1.0 vs 1.1 formatting
 
    subject_dn3 = subject_dn3.replace('subject=', '', 1).rstrip().replace(' /CN=', 'CN = ', 1)  # OpenSSL 1.0 vs 1.1 formatting
 

	
 
    # He notices that the all certificates seem to be have been issued
 
    # by correct entity.
 
    assert issuer_dn1 == subject_dn1
 
    assert issuer_dn2 == subject_dn1
 
    assert issuer_dn3 == subject_dn2
 
    assert subject_dn1 == 'CN = %s Level 1' % tmpdir.basename
 
    assert subject_dn2 == 'CN = %s Level 2' % tmpdir.basename
 
    assert subject_dn3 == 'CN = %s Level 3' % tmpdir.basename
 

	
 
    # John opens-up the chain file, and observes that all certificates
 
    # seem to be contained within.
 
    with open(".gimmecert/ca/level1.cert.pem", "r") as level1_cert_file:
 
        level1_cert = level1_cert_file.read()
 
    with open(".gimmecert/ca/level2.cert.pem", "r") as level2_cert_file:
 
        level2_cert = level2_cert_file.read()
 
    with open(".gimmecert/ca/level3.cert.pem", "r") as level3_cert_file:
 
        level3_cert = level3_cert_file.read()
 
    with open(".gimmecert/ca/chain-full.cert.pem", "r") as chain_full_file:
 
        chain_full = chain_full_file.read()
 

	
 
    assert level1_cert in chain_full
 
    assert level2_cert in chain_full
 
    assert level3_cert in chain_full
 

	
 
    # Just to make sure, John goes ahead and runs a command that
 
    # should verify the certificate signatures.
 
    _, _, error_code = run_command(
 
        "openssl", "verify",
 
        "-CAfile",
 
        ".gimmecert/ca/chain-full.cert.pem",
 
        ".gimmecert/ca/level1.cert.pem",
 
        ".gimmecert/ca/level2.cert.pem",
 
        ".gimmecert/ca/level3.cert.pem"
 
    )
 

	
 
    # He is happy to see that verification succeeds.
 
    assert error_code == 0
gimmecert/cli.py
Show inline comments
 
@@ -44,16 +44,18 @@ Examples:
 
def setup_init_subcommand_parser(parser, subparsers):
 
    subparser = subparsers.add_parser('init', description='Initialise CA hierarchy.')
 
    subparser.add_argument('--ca-base-name', '-b', help="Base name to use for CA naming. Default is to use the working directory base name.")
 
    subparser.add_argument('--ca-hierarchy-depth', '-d', type=int, help="Depth of CA hierarchy to generate. Default is 1", default=1)
 

	
 
    def init_wrapper(args):
 
        project_directory = os.getcwd()
 
        if args.ca_base_name is None:
 
            args.ca_base_name = os.path.basename(project_directory)
 

	
 
        if init(project_directory, args.ca_base_name):
 
        if init(project_directory, args.ca_base_name, args.ca_hierarchy_depth):
 
            print("CA hierarchy initialised. Generated artefacts:")
 
            print("    CA Level 1 private key: .gimmecert/ca/level1.key.pem")
 
            print("    CA Level 1 certificate: .gimmecert/ca/level1.cert.pem")
 
            for level in range(1, args.ca_hierarchy_depth+1):
 
                print("    CA Level %d private key: .gimmecert/ca/level%d.key.pem" % (level, level))
 
                print("    CA Level %d certificate: .gimmecert/ca/level%d.cert.pem" % (level, level))
 
            print("    Full certificate chain: .gimmecert/ca/chain-full.cert.pem")
 
        else:
 
            print("CA hierarchy has already been initialised.")
gimmecert/commands.py
Show inline comments
 
@@ -24,7 +24,7 @@ import gimmecert.crypto
 
import gimmecert.storage
 

	
 

	
 
def init(project_directory, ca_base_name):
 
def init(project_directory, ca_base_name, ca_hierarchy_depth):
 
    """
 
    Initialises the necessary directory and CA hierarchies for use in
 
    the specified directory.
 
@@ -35,6 +35,9 @@ def init(project_directory, ca_base_name):
 
    :param ca_base_name: Base name to use for constructing CA subject DNs.
 
    :type ca_base_name: str
 

	
 
    :param ca_hierarchy_depth: Length/depths of CA hierarchy that should be initialised. E.g. total number of CAs in chain.
 
    :type ca_hierarchy_depth: int
 

	
 
    :returns: False, if directory has been initialised in previous run, True if project has been initialised in this run.
 
    :rtype: bool
 
    """
 
@@ -42,9 +45,6 @@ def init(project_directory, ca_base_name):
 
    # Set-up various paths.
 
    base_directory = os.path.join(project_directory, '.gimmecert')
 
    ca_directory = os.path.join(base_directory, 'ca')
 
    level1_private_key_path = os.path.join(ca_directory, 'level1.key.pem')
 
    level1_certificate_path = os.path.join(ca_directory, 'level1.cert.pem')
 
    full_chain_path = os.path.join(ca_directory, 'chain-full.cert.pem')
 

	
 
    if os.path.exists(base_directory):
 
        return False
 
@@ -52,18 +52,19 @@ def init(project_directory, ca_base_name):
 
    # Initialise the directory.
 
    gimmecert.storage.initialise_storage(project_directory)
 

	
 
    # Generate private key and certificate.
 
    level1_dn = gimmecert.crypto.get_dn("%s Level 1" % ca_base_name)
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 
    level1_private_key = gimmecert.crypto.generate_private_key()
 
    level1_certificate = gimmecert.crypto.issue_certificate(level1_dn, level1_dn, level1_private_key, level1_private_key.public_key(), not_before, not_after)
 
    # Generate the CA hierarchy.
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy(ca_base_name, ca_hierarchy_depth)
 

	
 
    # Grab the full CA certificate chain.
 
    full_chain = level1_certificate
 
    # Output the CA private keys and certificates.
 
    for level, (private_key, certificate) in enumerate(ca_hierarchy, 1):
 
        private_key_path = os.path.join(ca_directory, 'level%d.key.pem' % level)
 
        certificate_path = os.path.join(ca_directory, 'level%d.cert.pem' % level)
 
        gimmecert.storage.write_private_key(private_key, private_key_path)
 
        gimmecert.storage.write_certificate(certificate, certificate_path)
 

	
 
    # Write-out the artifacts.
 
    gimmecert.storage.write_private_key(level1_private_key, level1_private_key_path)
 
    gimmecert.storage.write_certificate(level1_certificate, level1_certificate_path)
 
    gimmecert.storage.write_certificate(full_chain, full_chain_path)
 
    # Output the certificate chain.
 
    full_chain = [certificate for _, certificate in ca_hierarchy]
 
    full_chain_path = os.path.join(ca_directory, 'chain-full.cert.pem')
 
    gimmecert.storage.write_certificate_chain(full_chain, full_chain_path)
 

	
 
    return True
gimmecert/crypto.py
Show inline comments
 
@@ -84,7 +84,7 @@ def get_validity_range():
 
    return not_before, not_after
 

	
 

	
 
def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before, not_after):
 
def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before, not_after, extensions=None):
 
    """
 
    Issues a certificate using the passed-in data.
 

	
 
@@ -105,8 +105,17 @@ def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before
 

	
 
    :param not_after: End of certificate validity.
 
    :type datetime.datetime:
 

	
 
    :param extensions: List of certificate extensions with their criticality to add to resulting certificate object. List of (extension, criticality) pairs.
 
    :type extensions: list[(cryptography.x509.Extension, bool)]
 

	
 
    :returns: Issued certificate with requested content.
 
    :rtype: cryptography.x509.Certificate
 
    """
 

	
 
    if extensions is None:
 
        extensions = []
 

	
 
    builder = cryptography.x509.CertificateBuilder()
 
    builder = builder.subject_name(cryptography.x509.Name(subject_dn))
 
    builder = builder.issuer_name(cryptography.x509.Name(issuer_dn))
 
@@ -115,6 +124,9 @@ def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before
 
    builder = builder.serial_number(cryptography.x509.random_serial_number())
 
    builder = builder.public_key(public_key)
 

	
 
    for extension in extensions:
 
        builder = builder.add_extension(extension[0], critical=extension[1])
 

	
 
    certificate = builder.sign(
 
        private_key=signing_key,
 
        algorithm=cryptography.hazmat.primitives.hashes.SHA256(),
 
@@ -125,10 +137,25 @@ def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before
 

	
 

	
 
def generate_ca_hierarchy(base_name, depth):
 
    """
 
    Generates CA hierarchy with specified depth, using the provided
 
    naming as basis for the DNs.
 

	
 
    :param base_name: Base name for constructing the CA DNs. Resulting DNs are of format 'BASE Level N'.
 
    :type base_name: str
 

	
 
    :returns: List of CA private key and certificate pairs, starting with the level 1 (root) CA, and ending with the leaf CA.
 
    :rtype: list[(cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey, cryptography.x509.Certificate)]
 
    """
 

	
 
    hierarchy = []
 

	
 
    not_before, not_after = get_validity_range()
 

	
 
    extensions = [
 
        (cryptography.x509.BasicConstraints(ca=True, path_length=None), True)
 
    ]
 

	
 
    # We have not issued yet any certificate.
 
    issuer_dn = None
 
    issuer_private_key = None
 
@@ -142,7 +169,7 @@ def generate_ca_hierarchy(base_name, depth):
 
        issuer_dn = issuer_dn or dn
 
        issuer_private_key = issuer_private_key or private_key
 

	
 
        certificate = issue_certificate(issuer_dn, dn, issuer_private_key, private_key.public_key(), not_before, not_after)
 
        certificate = issue_certificate(issuer_dn, dn, issuer_private_key, private_key.public_key(), not_before, not_after, extensions)
 
        hierarchy.append((private_key, certificate))
 

	
 
        # Current entity becomes issuer for next one in chain.
tests/test_cli.py
Show inline comments
 
@@ -163,21 +163,25 @@ def test_setup_init_subcommand_sets_function_callback():
 
@mock.patch('sys.argv', ['gimmecert', 'init'])
 
@mock.patch('gimmecert.cli.init')
 
def test_init_command_invoked_with_correct_parameters_no_options(mock_init, tmpdir):
 
    default_depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.cli.main()
 

	
 
    mock_init.assert_called_once_with(tmpdir.strpath, tmpdir.basename)
 
    mock_init.assert_called_once_with(tmpdir.strpath, tmpdir.basename, default_depth)
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'init', '-b', 'My Project'])
 
@mock.patch('gimmecert.cli.init')
 
def test_init_command_invoked_with_correct_parameters_with_options(mock_init, tmpdir):
 
    default_depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.cli.main()
 

	
 
    mock_init.assert_called_once_with(tmpdir.strpath, 'My Project')
 
    mock_init.assert_called_once_with(tmpdir.strpath, 'My Project', default_depth)
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'init', '--ca-base-name', 'My Project'])
 
@@ -190,3 +194,15 @@ def test_init_command_accepts_ca_base_name_option_long_form():
 
def test_init_command_accepts_ca_base_name_option_short_form():
 

	
 
    gimmecert.cli.main()  # Should not raise
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'init', '--ca-hierarchy-depth', '3'])
 
def test_init_command_accepts_ca_hierarchy_depth_option_long_form():
 

	
 
    gimmecert.cli.main()  # Should not raise
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'init', '-d', '3'])
 
def test_init_command_accepts_ca_hierarchy_depth_option_short_form():
 

	
 
    gimmecert.cli.main()  # Should not raise
tests/test_commands.py
Show inline comments
 
@@ -26,52 +26,107 @@ import gimmecert.commands
 
def test_init_sets_up_directory_structure(tmpdir):
 
    base_dir = tmpdir.join('.gimmecert')
 
    ca_dir = tmpdir.join('.gimmecert')
 
    depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    assert os.path.exists(base_dir.strpath)
 
    assert os.path.exists(ca_dir.strpath)
 

	
 

	
 
def test_init_generates_ca_artifacts(tmpdir):
 
def test_init_generates_single_ca_artifact_for_depth_1(tmpdir):
 
    depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').strpath)
 

	
 

	
 
def test_init_generates_three_ca_artifacts_for_depth_3(tmpdir):
 
    depth = 3
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level2.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level2.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level3.key.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'level3.cert.pem').strpath)
 
    assert os.path.exists(tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').strpath)
 

	
 

	
 
def test_init_outputs_full_chain_for_depth_1(tmpdir):
 
    depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    level1_certificate = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    full_chain = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 
    assert level1_certificate == full_chain
 
    assert full_chain.replace(level1_certificate, '') == ''
 

	
 

	
 
def test_init_outputs_full_chain_for_depth_3(tmpdir):
 
    depth = 3
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    level1_certificate = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    level2_certificate = tmpdir.join('.gimmecert', 'ca', 'level2.cert.pem').read()
 
    level3_certificate = tmpdir.join('.gimmecert', 'ca', 'level3.cert.pem').read()
 
    full_chain = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 
    assert level1_certificate in full_chain
 
    assert level2_certificate in full_chain
 
    assert level3_certificate in full_chain
 
    assert full_chain == "%s\n%s\n%s" % (level1_certificate, level2_certificate, level3_certificate)
 

	
 

	
 
def test_init_returns_true_if_directory_has_not_been_previously_initialised(tmpdir):
 
    depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    initialised = gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    initialised = gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    assert initialised is True
 

	
 

	
 
def test_init_returns_false_if_directory_has_been_previously_initialised(tmpdir):
 
    depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    initialised = gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 
    initialised = gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    assert initialised is False
 

	
 

	
 
def test_init_does_not_overwrite_artifcats_if_already_initialised(tmpdir):
 
    depth = 1
 

	
 
    tmpdir.chdir()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    level1_private_key_before = tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').read()
 
    level1_certificate_before = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
 
    full_chain_before = tmpdir.join('.gimmecert', 'ca', 'chain-full.cert.pem').read()
 

	
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename)
 
    gimmecert.commands.init(tmpdir.strpath, tmpdir.basename, depth)
 

	
 
    level1_private_key_after = tmpdir.join('.gimmecert', 'ca', 'level1.key.pem').read()
 
    level1_certificate_after = tmpdir.join('.gimmecert', 'ca', 'level1.cert.pem').read()
tests/test_crypto.py
Show inline comments
 
@@ -22,6 +22,7 @@
 
import datetime
 

	
 
import cryptography.hazmat.primitives.asymmetric.rsa
 
import cryptography.x509
 
from dateutil.relativedelta import relativedelta
 

	
 
import gimmecert.crypto
 
@@ -205,3 +206,55 @@ def test_generate_ca_hierarchy_certificates_have_same_validity():
 

	
 
    assert level1_certificate.not_valid_before == level2_certificate.not_valid_before == level3_certificate.not_valid_before
 
    assert level1_certificate.not_valid_after == level2_certificate.not_valid_after == level3_certificate.not_valid_after
 

	
 

	
 
def test_issue_certificate_sets_extensions():
 
    dn = gimmecert.crypto.get_dn('My test 1')
 
    private_key = gimmecert.crypto.generate_private_key()
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 
    basic_constraints = cryptography.x509.BasicConstraints(ca=True, path_length=None)
 
    ocsp_no_check = cryptography.x509.OCSPNoCheck()
 
    extensions = [
 
        (basic_constraints, True),
 
        (ocsp_no_check, False),
 
    ]
 

	
 
    certificate = gimmecert.crypto.issue_certificate(dn, dn, private_key, private_key.public_key(), not_before, not_after, extensions)
 

	
 
    assert len(certificate.extensions) == 2
 

	
 
    stored_extension = certificate.extensions.get_extension_for_class(cryptography.x509.BasicConstraints)
 
    assert stored_extension.value == basic_constraints
 
    assert stored_extension.critical is True
 

	
 
    stored_extension = certificate.extensions.get_extension_for_class(cryptography.x509.OCSPNoCheck)
 
    assert stored_extension.critical is False
 
    assert isinstance(stored_extension.value, cryptography.x509.OCSPNoCheck)
 

	
 

	
 
def test_issue_certificate_sets_no_extensions_if_none_are_passed():
 
    dn = gimmecert.crypto.get_dn('My test 1')
 
    private_key = gimmecert.crypto.generate_private_key()
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 

	
 
    certificate1 = gimmecert.crypto.issue_certificate(dn, dn, private_key, private_key.public_key(), not_before, not_after, None)
 
    certificate2 = gimmecert.crypto.issue_certificate(dn, dn, private_key, private_key.public_key(), not_before, not_after, [])
 

	
 
    assert len(certificate1.extensions) == 0
 
    assert len(certificate2.extensions) == 0
 

	
 

	
 
def test_generate_ca_hierarchy_produces_certificates_with_ca_basic_constraints():
 
    base_name = 'My test'
 
    depth = 3
 

	
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth)
 

	
 
    for _, certificate in hierarchy:
 
        stored_extension = certificate.extensions.get_extension_for_class(cryptography.x509.BasicConstraints)
 
        value, critical = stored_extension.value, stored_extension.critical
 

	
 
        assert isinstance(value, cryptography.x509.BasicConstraints)
 
        assert critical is True
 
        assert value.ca is True
 
        assert value.path_length is None
0 comments (0 inline, 0 general)