Skip to content

Commit

Permalink
Use timezone aware UTC time starting with Cryptography 42.0.0. (aiort…
Browse files Browse the repository at this point in the history
  • Loading branch information
rthalley committed Jan 23, 2024
1 parent 5bbac3b commit 488f1ab
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
with:
python-version: 3.8
- name: Install packages
run: pip install mypy ruff types-certifi types-cryptography types-pyopenssl
run: pip install mypy ruff types-certifi types-pyopenssl
- name: Run linters
run: |
ruff .
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ classifiers = [
]
dependencies = [
"certifi",
"cryptography",
"cryptography>=42.0.0",
"pylsqpack>=0.3.3,<0.4.0",
"pyopenssl>=22",
"pyopenssl>=24",
"service-identity>=24.1.0",
]
dynamic = ["version"]
Expand Down
28 changes: 22 additions & 6 deletions src/aioquic/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Tuple,
TypeVar,
Union,
cast,
)

import certifi
Expand All @@ -37,6 +38,10 @@
x448,
x25519,
)
from cryptography.hazmat.primitives.asymmetric.types import (
CertificateIssuerPublicKeyTypes,
PrivateKeyTypes,
)
from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from OpenSSL import crypto
Expand All @@ -54,8 +59,10 @@

T = TypeVar("T")


# facilitate mocking for the test suite
utcnow = datetime.datetime.utcnow
def utcnow() -> datetime.datetime:
return datetime.datetime.now(datetime.timezone.utc)


class AlertDescription(IntEnum):
Expand Down Expand Up @@ -191,7 +198,7 @@ def hkdf_extract(

def load_pem_private_key(
data: bytes, password: Optional[bytes] = None
) -> Union[dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey]:
) -> PrivateKeyTypes:
"""
Load a PEM-encoded private key.
"""
Expand Down Expand Up @@ -220,9 +227,9 @@ def verify_certificate(
) -> None:
# verify dates
now = utcnow()
if now < certificate.not_valid_before:
if now < certificate.not_valid_before_utc:
raise AlertCertificateExpired("Certificate is not valid yet")
if now > certificate.not_valid_after:
if now > certificate.not_valid_after_utc:
raise AlertCertificateExpired("Certificate is no longer valid")

# verify subject
Expand Down Expand Up @@ -1082,7 +1089,7 @@ def update_hash(self, data: bytes) -> None:
k.update_hash(data)


CIPHER_SUITES = {
CIPHER_SUITES: Dict = {
CipherSuite.AES_128_GCM_SHA256: hashes.SHA256,
CipherSuite.AES_256_GCM_SHA384: hashes.SHA384,
CipherSuite.CHACHA20_POLY1305_SHA256: hashes.SHA256,
Expand Down Expand Up @@ -1471,7 +1478,16 @@ def _check_certificate_verify_signature(self, verify: CertificateVerify) -> None
)

try:
self._peer_certificate.public_key().verify(
# The type of public_key() is CertificatePublicKeyTypes, but along with
# ed25519 and ed448, which are fine, this type includes
# x25519 and x448 which can be public keys but can't sign. We know
# we won't get x25519 and x448 as they are not on our list of
# signature algorithms, so we can cast public key to
# CertificateIssuerPublicKeyTypes safely and make mypy happy.
public_key = cast(
CertificateIssuerPublicKeyTypes, self._peer_certificate.public_key()
)
public_key.verify(
verify.signature,
self.key_schedule.certificate_verify_data(
SERVER_CONTEXT_STRING if self._is_client else CLIENT_CONTEXT_STRING
Expand Down
20 changes: 10 additions & 10 deletions tests/test_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,7 @@ def test_verify_certificate_chain(self):
certificate = load_pem_x509_certificates(fp.read())[0]

with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_before
mock_utcnow.return_value = certificate.not_valid_before_utc

# fail
with self.assertRaises(tls.AlertBadCertificate) as cm:
Expand All @@ -1592,7 +1592,7 @@ def test_verify_certificate_chain_self_signed(self):
)

with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_before
mock_utcnow.return_value = certificate.not_valid_before_utc

# fail
with self.assertRaises(tls.AlertBadCertificate) as cm:
Expand Down Expand Up @@ -1621,7 +1621,7 @@ def test_verify_dates(self):
#  too early
with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = (
certificate.not_valid_before - datetime.timedelta(seconds=1)
certificate.not_valid_before_utc - datetime.timedelta(seconds=1)
)
with self.assertRaises(tls.AlertCertificateExpired) as cm:
verify_certificate(
Expand All @@ -1631,21 +1631,21 @@ def test_verify_dates(self):

# valid
with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_before
mock_utcnow.return_value = certificate.not_valid_before_utc
verify_certificate(
cadata=cadata, certificate=certificate, server_name="example.com"
)

with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_after
mock_utcnow.return_value = certificate.not_valid_after_utc
verify_certificate(
cadata=cadata, certificate=certificate, server_name="example.com"
)

# too late
with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_after + datetime.timedelta(
seconds=1
mock_utcnow.return_value = (
certificate.not_valid_after_utc + datetime.timedelta(seconds=1)
)
with self.assertRaises(tls.AlertCertificateExpired) as cm:
verify_certificate(
Expand All @@ -1658,7 +1658,7 @@ def test_verify_subject_no_subjaltname(self):
cadata = certificate.public_bytes(serialization.Encoding.PEM)

with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_before
mock_utcnow.return_value = certificate.not_valid_before_utc

# certificates with no SubjectAltName are rejected
with self.assertRaises(tls.AlertBadCertificate) as cm:
Expand All @@ -1677,7 +1677,7 @@ def test_verify_subject_with_subjaltname(self):
cadata = certificate.public_bytes(serialization.Encoding.PEM)

with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_before
mock_utcnow.return_value = certificate.not_valid_before_utc

# valid
verify_certificate(
Expand Down Expand Up @@ -1707,7 +1707,7 @@ def test_verify_subject_with_subjaltname_ipaddress(self):
cadata = certificate.public_bytes(serialization.Encoding.PEM)

with patch("aioquic.tls.utcnow") as mock_utcnow:
mock_utcnow.return_value = certificate.not_valid_before
mock_utcnow.return_value = certificate.not_valid_before_utc

# valid
verify_certificate(
Expand Down
6 changes: 4 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ def generate_certificate(*, alternative_names, common_name, hash_algorithm, key)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))
.not_valid_before(datetime.datetime.now(datetime.timezone.utc))
.not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10)
)
)
if alternative_names:
builder = builder.add_extension(
Expand Down

0 comments on commit 488f1ab

Please sign in to comment.