Changeset - 4f3c2c135344
[Not reviewed]
0 3 0
Branko Majic (branko) - 2 months ago 2024-02-23 22:45:03
branko@majic.rs
GC-45: Use non-naive datetime objects that include timezone:

- Newer versions of cryptography prefer/insists on use of UTC-based
datetime objects with correctly set timezone.
3 files changed with 56 insertions and 46 deletions:
0 comments (0 inline, 0 general)
gimmecert/commands.py
Show inline comments
 
@@ -520,13 +520,13 @@ def status(stdout, stderr, project_directory):
 
    :type project_directory: str
 

	
 
    :returns: Status code, one from gimmecert.commands.ExitCode.
 
    :rtype: int
 
    """
 

	
 
    now = datetime.datetime.now()
 
    now = datetime.datetime.now(datetime.timezone.utc)
 

	
 
    if not gimmecert.storage.is_initialised(project_directory):
 
        print("CA hierarchy has not been initialised in current directory.", file=stdout)
 
        return ExitCode.ERROR_NOT_INITIALISED
 

	
 
    def get_section_title(title):
 
@@ -553,21 +553,21 @@ def status(stdout, stderr, project_directory):
 

	
 
        if i == len(ca_hierarchy):
 
            print(gimmecert.utils.dn_to_str(certificate.subject) + " [END ENTITY ISSUING CA]", file=stdout)
 
        else:
 
            print(gimmecert.utils.dn_to_str(certificate.subject), file=stdout)
 

	
 
        if certificate.not_valid_before > now:
 
        if certificate.not_valid_before_utc > now:
 
            validity_status = " [NOT VALID YET]"
 
        elif certificate.not_valid_after < now:
 
        elif certificate.not_valid_after_utc < now:
 
            validity_status = " [EXPIRED]"
 
        else:
 
            validity_status = ""
 

	
 
        print("    Validity: %s%s" % (gimmecert.utils.date_range_to_str(certificate.not_valid_before,
 
                                                                        certificate.not_valid_after),
 
        print("    Validity: %s%s" % (gimmecert.utils.date_range_to_str(certificate.not_valid_before_utc,
 
                                                                        certificate.not_valid_after_utc),
 
                                      validity_status), file=stdout)
 
        print("    Certificate: .gimmecert/ca/level%d.cert.pem" % i, file=stdout)
 

	
 
    # Separator.
 
    print("", file=stdout)
 

	
 
@@ -587,22 +587,22 @@ def status(stdout, stderr, project_directory):
 
            csr_path = os.path.join(project_directory, '.gimmecert', 'server', certificate_file.replace('.cert.pem', '.csr.pem'))
 
            key_algorithm = str(gimmecert.crypto.KeyGenerator(*gimmecert.crypto.key_specification_from_public_key(certificate.public_key())))
 

	
 
            # Separator.
 
            print("", file=stdout)
 

	
 
            if certificate.not_valid_before > now:
 
            if certificate.not_valid_before_utc > now:
 
                validity_status = " [NOT VALID YET]"
 
            elif certificate.not_valid_after < now:
 
            elif certificate.not_valid_after_utc < now:
 
                validity_status = " [EXPIRED]"
 
            else:
 
                validity_status = ""
 

	
 
            print(gimmecert.utils.dn_to_str(certificate.subject), file=stdout)
 
            print("    Validity: %s%s" % (gimmecert.utils.date_range_to_str(certificate.not_valid_before,
 
                                                                            certificate.not_valid_after),
 
            print("    Validity: %s%s" % (gimmecert.utils.date_range_to_str(certificate.not_valid_before_utc,
 
                                                                            certificate.not_valid_after_utc),
 
                                          validity_status), file=stdout)
 
            print("    DNS: %s" % ", ".join(gimmecert.utils.get_dns_names(certificate)), file=stdout)
 

	
 
            print("    Key algorithm: %s" % key_algorithm, file=stdout)
 
            if os.path.exists(private_key_path):
 
                print("    Private key: .gimmecert/server/%s" % certificate_file.replace('.cert.pem', '.key.pem'), file=stdout)
 
@@ -629,22 +629,22 @@ def status(stdout, stderr, project_directory):
 
            csr_path = os.path.join(project_directory, '.gimmecert', 'client', certificate_file.replace('.cert.pem', '.csr.pem'))
 
            key_algorithm = str(gimmecert.crypto.KeyGenerator(*gimmecert.crypto.key_specification_from_public_key(certificate.public_key())))
 

	
 
            # Separator.
 
            print("", file=stdout)
 

	
 
            if certificate.not_valid_before > now:
 
            if certificate.not_valid_before_utc > now:
 
                validity_status = " [NOT VALID YET]"
 
            elif certificate.not_valid_after < now:
 
            elif certificate.not_valid_after_utc < now:
 
                validity_status = " [EXPIRED]"
 
            else:
 
                validity_status = ""
 

	
 
            print(gimmecert.utils.dn_to_str(certificate.subject), file=stdout)
 
            print("    Validity: %s%s" % (gimmecert.utils.date_range_to_str(certificate.not_valid_before,
 
                                                                            certificate.not_valid_after),
 
            print("    Validity: %s%s" % (gimmecert.utils.date_range_to_str(certificate.not_valid_before_utc,
 
                                                                            certificate.not_valid_after_utc),
 
                                          validity_status), file=stdout)
 

	
 
            print("    Key algorithm: %s" % key_algorithm, file=stdout)
 
            if os.path.exists(private_key_path):
 
                print("    Private key: .gimmecert/client/%s" % certificate_file.replace('.cert.pem', '.key.pem'), file=stdout)
 
            elif os.path.exists(csr_path):
gimmecert/crypto.py
Show inline comments
 
@@ -124,13 +124,13 @@ def get_validity_range():
 
    second (microseconds are discarded).
 

	
 
    :returns: (not_before, not_after) -- Tuple defining the time range.
 
    :rtype: (datetime.datetime, datetime.datetime)
 
    """
 

	
 
    now = datetime.datetime.utcnow().replace(microsecond=0)
 
    now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
 
    not_before = now - datetime.timedelta(minutes=15)
 
    not_after = now + relativedelta(years=1)
 

	
 
    return not_before, not_after
 

	
 

	
 
@@ -290,17 +290,17 @@ def issue_server_certificate(name, public_key, issuer_private_key, issuer_certif
 
            ), True
 
        ),
 
        (cryptography.x509.ExtendedKeyUsage([cryptography.x509.oid.ExtendedKeyUsageOID.SERVER_AUTH]), True),
 
        (cryptography.x509.SubjectAlternativeName([cryptography.x509.DNSName(dns_name) for dns_name in dns_names]), False)
 
    ]
 

	
 
    if not_before < issuer_certificate.not_valid_before:
 
        not_before = issuer_certificate.not_valid_before
 
    if not_before < issuer_certificate.not_valid_before_utc:
 
        not_before = issuer_certificate.not_valid_before_utc
 

	
 
    if not_after > issuer_certificate.not_valid_after:
 
        not_after = issuer_certificate.not_valid_after
 
    if not_after > issuer_certificate.not_valid_after_utc:
 
        not_after = issuer_certificate.not_valid_after_utc
 

	
 
    certificate = issue_certificate(issuer_certificate.subject, dn, issuer_private_key, public_key, not_before, not_after, extensions)
 

	
 
    return certificate
 

	
 

	
 
@@ -350,17 +350,17 @@ def issue_client_certificate(name, public_key, issuer_private_key, issuer_certif
 
                decipher_only=False
 
            ), True
 
        ),
 
        (cryptography.x509.ExtendedKeyUsage([cryptography.x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH]), True),
 
    ]
 

	
 
    if not_before < issuer_certificate.not_valid_before:
 
        not_before = issuer_certificate.not_valid_before
 
    if not_before < issuer_certificate.not_valid_before_utc:
 
        not_before = issuer_certificate.not_valid_before_utc
 

	
 
    if not_after > issuer_certificate.not_valid_after:
 
        not_after = issuer_certificate.not_valid_after
 
    if not_after > issuer_certificate.not_valid_after_utc:
 
        not_after = issuer_certificate.not_valid_after_utc
 

	
 
    certificate = issue_certificate(issuer_certificate.subject, dn, issuer_private_key, public_key, not_before, not_after, extensions)
 

	
 
    return certificate
 

	
 

	
 
@@ -386,17 +386,17 @@ def renew_certificate(old_certificate, public_key, issuer_private_key, issuer_ce
 
    :returns: New certificate, which preserves naming, extensions, and public key of the old one.
 
    :rtype: cryptography.x509.Certificate
 
    """
 

	
 
    not_before, not_after = get_validity_range()
 

	
 
    if not_before < issuer_certificate.not_valid_before:
 
        not_before = issuer_certificate.not_valid_before
 
    if not_before < issuer_certificate.not_valid_before_utc:
 
        not_before = issuer_certificate.not_valid_before_utc
 

	
 
    if not_after > issuer_certificate.not_valid_after:
 
        not_after = issuer_certificate.not_valid_after
 
    if not_after > issuer_certificate.not_valid_after_utc:
 
        not_after = issuer_certificate.not_valid_after_utc
 

	
 
    new_certificate = issue_certificate(issuer_certificate.subject,
 
                                        old_certificate.subject,
 
                                        issuer_private_key,
 
                                        public_key,
 
                                        not_before,
tests/test_crypto.py
Show inline comments
 
@@ -45,17 +45,27 @@ def test_get_validity_range_returns_datetime_tuple():
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 

	
 
    assert isinstance(not_before, datetime.datetime)
 
    assert isinstance(not_after, datetime.datetime)
 

	
 

	
 
def test_get_validity_range_sets_utc_timezone():
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 

	
 
    assert isinstance(not_before.tzinfo, datetime.timezone)
 
    assert not_before.tzinfo == datetime.timezone.utc
 

	
 
    assert isinstance(not_after.tzinfo, datetime.timezone)
 
    assert not_after.tzinfo == datetime.timezone.utc
 

	
 

	
 
@travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False)
 
def test_get_validity_range_not_before_is_within_15_minutes_of_now():
 
    not_before, _ = gimmecert.crypto.get_validity_range()
 

	
 
    assert not_before == datetime.datetime(2018, 1, 1, 0, 0)
 
    assert not_before == datetime.datetime(2018, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
 

	
 

	
 
@travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False)
 
def test_get_validity_range_is_one_year_and_15_minutes():
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 
    difference = relativedelta(not_after, not_before)
 
@@ -92,14 +102,14 @@ def test_issue_certificate_has_correct_content():
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 

	
 
    certificate = gimmecert.crypto.issue_certificate(issuer_dn, subject_dn, issuer_private_key, subject_private_key.public_key(), not_before, not_after)
 

	
 
    assert certificate.issuer == issuer_dn
 
    assert certificate.subject == subject_dn
 
    assert certificate.not_valid_before == not_before
 
    assert certificate.not_valid_after == not_after
 
    assert certificate.not_valid_before_utc == not_before
 
    assert certificate.not_valid_after_utc == not_after
 

	
 

	
 
def test_generate_ca_hierarchy_returns_list_with_3_elements_for_depth_3():
 
    base_name = 'My Project'
 
    depth = 3
 

	
 
@@ -210,14 +220,14 @@ def test_generate_ca_hierarchy_certificates_have_same_validity():
 
    hierarchy = gimmecert.crypto.generate_ca_hierarchy(base_name, depth, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    _, level1_certificate = hierarchy[0]
 
    _, level2_certificate = hierarchy[1]
 
    _, level3_certificate = hierarchy[2]
 

	
 
    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
 
    assert level1_certificate.not_valid_before_utc == level2_certificate.not_valid_before_utc == level3_certificate.not_valid_before_utc
 
    assert level1_certificate.not_valid_after_utc == level2_certificate.not_valid_after_utc == level3_certificate.not_valid_after_utc
 

	
 

	
 
def test_issue_certificate_sets_extensions():
 
    dn = gimmecert.crypto.get_dn('My test 1')
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 
    not_before, not_after = gimmecert.crypto.get_validity_range()
 
@@ -358,41 +368,41 @@ def test_issue_server_certificate_not_before_is_15_minutes_in_past():
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 

	
 
    certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_before == datetime.datetime(2018, 1, 1, 0, 0)
 
    assert certificate.not_valid_before_utc == datetime.datetime(2018, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
 

	
 

	
 
def test_issue_server_certificate_not_before_does_not_exceed_ca_validity():
 
    with travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 

	
 
    with travel(issuer_certificate.not_valid_before - datetime.timedelta(seconds=1), tick=False):
 
    with travel(issuer_certificate.not_valid_before_utc - datetime.timedelta(seconds=1), tick=False):
 
        certificate1 = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate1.not_valid_before == issuer_certificate.not_valid_before
 
    assert certificate1.not_valid_before_utc == issuer_certificate.not_valid_before_utc
 

	
 

	
 
def test_issue_server_certificate_not_after_does_not_exceed_ca_validity():
 
    with travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 

	
 
    with travel(issuer_certificate.not_valid_after + datetime.timedelta(seconds=1), tick=False):
 
    with travel(issuer_certificate.not_valid_after_utc + datetime.timedelta(seconds=1), tick=False):
 
        certificate1 = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate1.not_valid_after == issuer_certificate.not_valid_after
 
    assert certificate1.not_valid_after_utc == issuer_certificate.not_valid_after_utc
 

	
 

	
 
def test_issue_server_certificate_incorporates_additional_dns_subject_alternative_names():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
@@ -494,41 +504,41 @@ def test_issue_client_certificate_not_before_is_15_minutes_in_past():
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 

	
 
    certificate = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_before == datetime.datetime(2018, 1, 1, 0, 0)
 
    assert certificate.not_valid_before_utc == datetime.datetime(2018, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
 

	
 

	
 
def test_issue_client_certificate_not_before_does_not_exceed_ca_validity():
 
    with travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 

	
 
    with travel(issuer_certificate.not_valid_before - datetime.timedelta(seconds=1), tick=False):
 
    with travel(issuer_certificate.not_valid_before_utc - datetime.timedelta(seconds=1), tick=False):
 
        certificate1 = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate1.not_valid_before == issuer_certificate.not_valid_before
 
    assert certificate1.not_valid_before_utc == issuer_certificate.not_valid_before_utc
 

	
 

	
 
def test_issue_client_certificate_not_after_does_not_exceed_ca_validity():
 
    with travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False):
 
        ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 

	
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 

	
 
    with travel(issuer_certificate.not_valid_after + datetime.timedelta(seconds=1), tick=False):
 
    with travel(issuer_certificate.not_valid_after_utc + datetime.timedelta(seconds=1), tick=False):
 
        certificate1 = gimmecert.crypto.issue_client_certificate('myclient', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate1.not_valid_after == issuer_certificate.not_valid_after
 
    assert certificate1.not_valid_after_utc == issuer_certificate.not_valid_after_utc
 

	
 

	
 
def test_renew_certificate_returns_certificate():
 
    ca_hierarchy = gimmecert.crypto.generate_ca_hierarchy('My Project', 1, gimmecert.crypto.KeyGenerator("rsa", 2048))
 
    issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
@@ -572,13 +582,13 @@ def test_renew_certificate_not_before_is_15_minutes_in_past():
 
        old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    # Renew certificate.
 
    with travel(datetime.datetime(2018, 6, 1, 0, 15, 0), tick=False):
 
        certificate = gimmecert.crypto.renew_certificate(old_certificate, private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_before == datetime.datetime(2018, 6, 1, 0, 0)
 
    assert certificate.not_valid_before_utc == datetime.datetime(2018, 6, 1, 0, 0, tzinfo=datetime.timezone.utc)
 

	
 

	
 
def test_renew_certificate_not_before_does_not_exceed_ca_validity():
 

	
 
    # Initial server certificate.
 
    with travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False):
 
@@ -586,16 +596,16 @@ def test_renew_certificate_not_before_does_not_exceed_ca_validity():
 
        issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
        private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 
        old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    # Renew certificate.
 
    with travel(issuer_certificate.not_valid_before - datetime.timedelta(seconds=1), tick=False):
 
    with travel(issuer_certificate.not_valid_before_utc - datetime.timedelta(seconds=1), tick=False):
 
        certificate = gimmecert.crypto.renew_certificate(old_certificate, private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_before == issuer_certificate.not_valid_before
 
    assert certificate.not_valid_before_utc == issuer_certificate.not_valid_before_utc
 

	
 

	
 
def test_renew_certificate_not_after_does_not_exceed_ca_validity():
 

	
 
    # Initial server certificate.
 
    with travel(datetime.datetime(2018, 1, 1, 0, 15, 0), tick=False):
 
@@ -603,16 +613,16 @@ def test_renew_certificate_not_after_does_not_exceed_ca_validity():
 
        issuer_private_key, issuer_certificate = ca_hierarchy[0]
 

	
 
        private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 
        old_certificate = gimmecert.crypto.issue_server_certificate('myserver', private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    # Renew certificate.
 
    with travel(issuer_certificate.not_valid_after + datetime.timedelta(seconds=1), tick=False):
 
    with travel(issuer_certificate.not_valid_after_utc + datetime.timedelta(seconds=1), tick=False):
 
        certificate = gimmecert.crypto.renew_certificate(old_certificate, private_key.public_key(), issuer_private_key, issuer_certificate)
 

	
 
    assert certificate.not_valid_after == issuer_certificate.not_valid_after
 
    assert certificate.not_valid_after_utc == issuer_certificate.not_valid_after_utc
 

	
 

	
 
def test_generate_csr_returns_csr_with_passed_in_dn():
 

	
 
    private_key = gimmecert.crypto.KeyGenerator('rsa', 2048)()
 
    subject_dn = gimmecert.crypto.get_dn('testcsr')
0 comments (0 inline, 0 general)