import datetime
import math
from collections import namedtuple
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
CertKeyPair = namedtuple('CertKeyPair', ['cert', 'key'])
CertificateDetails = namedtuple('CertificateDetails', 'pem thumbprint')
def prepare_certificate(subject_name: x509.Name, issuer_name: x509.Name, valid_days: int, public_key) -> x509.CertificateBuilder:
builder = x509.CertificateBuilder()
builder = builder.subject_name(subject_name)
builder = builder.issuer_name(issuer_name)
today = datetime.datetime.today()
# NOTE: this is the line which (sometimes) fails with `OverflowError: cannot convert float infinity to integer`
# despite `valid_days` being a normal integer (e.g. 730 or 365) as confirmed in pdb
builder = builder.not_valid_before(today - datetime.timedelta(days=math.floor(float(valid_days) * 0.3)))
builder = builder.not_valid_after(today + datetime.timedelta(days=math.ceil(float(valid_days) * 0.7)))
builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(public_key)
return builder
def create_ca_certificate(subject_name: x509.Name) -> CertKeyPair:
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
builder = prepare_certificate(subject_name, subject_name, 3650, public_key)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True,
)
builder = builder.add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False
), critical=True,
)
subject_key_id = x509.SubjectKeyIdentifier.from_public_key(public_key)
builder = builder.add_extension(subject_key_id, critical=False)
builder = builder.add_extension(x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(subject_key_id), critical=False)
certificate = builder.sign(private_key=private_key, algorithm=hashes.SHA256())
return CertKeyPair(cert=certificate, key=private_key)
def create_intermediate_certificate(subject_name: x509.Name, issuer: CertKeyPair) -> CertKeyPair:
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
builder = prepare_certificate(subject_name, issuer.cert.subject, 730, public_key)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=1), critical=True,
)
builder = builder.add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False
), critical=True,
)
builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False)
builder = builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
issuer.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
), critical=False
)
certificate = builder.sign(private_key=private_key, algorithm=hashes.SHA256())
return CertKeyPair(cert=certificate, key=private_key)
def create_leaf_certificate(subject_name: x509.Name, issuer: CertKeyPair) -> CertKeyPair:
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
builder = prepare_certificate(subject_name, issuer.cert.subject, 365, public_key)
builder = builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None), critical=True,
)
builder = builder.add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=True,
key_encipherment=True,
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False
), critical=True,
)
builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False)
builder = builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
issuer.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
), critical=False
)
certificate = builder.sign(private_key=private_key, algorithm=hashes.SHA256())
return CertKeyPair(cert=certificate, key=private_key)
# Normally it fails in one of `create_intermediate_certificate` or `create_leaf_certificate`
# with an error in `prepare_certificate` like this:
# builder = builder.not_valid_before(today - datetime.timedelta(days=math.floor(float(valid_days) * 0.3)))
# ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
# OverflowError: cannot convert float infinity to integer
# though sometimes it runs successfully.
# This behaviour started since upgrading to cryptography==46.0.5
foo_ca = create_ca_certificate(
subject_name=x509.Name.from_rfc4514_string(
'C=GB,ST=England,O=Foo Ltd,CN=Foo Ltd CA'))
foo_int_ca = create_intermediate_certificate(
subject_name=x509.Name.from_rfc4514_string(
'C=GB,ST=England,O=Foo Ltd,CN=Foo Ltd Intermediate CA'),
issuer=foo_ca)
foo_signing_cert = create_leaf_certificate(
subject_name=x509.Name.from_rfc4514_string(
'C=GB,ST=England,O=Foo Ltd,CN=Foo Ltd Signing Certificate'),
issuer=foo_int_ca)
Problem
Sometimes (but not always) when running the test code below on python 3.14 on Windows it fails with this very odd error:
despite
valid_daysbeing 730 where of course we expect this simple maths to work just fine:This only seems to happen with certain
cryptographyversions, which is why I'm filing it here, despite the error occurring in a bit of basic python maths in a very strange way.Environment
This repros on cryptography versions:
But cryptography==44.0.3 does not repro after many attempts.
Repro code
EDIT: see this comment for a slightly smaller repro script which loops until it breaks.
Original repro code