Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PrivateKeyUsagePeriod extension #11243

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/cryptography/hazmat/_oid.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ExtensionOID:
SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9")
SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14")
KEY_USAGE = ObjectIdentifier("2.5.29.15")
PRIVATE_KEY_USAGE_PERIOD = ObjectIdentifier("2.5.29.16")
SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17")
ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18")
BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
Expand Down Expand Up @@ -273,6 +274,7 @@ class AttributeOID:
ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes",
ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier",
ExtensionOID.KEY_USAGE: "keyUsage",
ExtensionOID.PRIVATE_KEY_USAGE_PERIOD: "privateKeyUsagePeriod",
ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName",
ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName",
ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints",
Expand Down
3 changes: 3 additions & 0 deletions src/cryptography/x509/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
PolicyInformation,
PrecertificateSignedCertificateTimestamps,
PrecertPoison,
PrivateKeyUsagePeriod,
ReasonFlags,
SignedCertificateTimestamps,
SubjectAlternativeName,
Expand Down Expand Up @@ -111,6 +112,7 @@
OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY
OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME
OID_KEY_USAGE = ExtensionOID.KEY_USAGE
OID_PRIVATE_KEY_USAGE_PERIOD = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD
OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS
OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK
OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS
Expand Down Expand Up @@ -226,6 +228,7 @@
"PolicyInformation",
"PrecertPoison",
"PrecertificateSignedCertificateTimestamps",
"PrivateKeyUsagePeriod",
"PublicKeyAlgorithmOID",
"RFC822Name",
"ReasonFlags",
Expand Down
62 changes: 62 additions & 0 deletions src/cryptography/x509/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,68 @@ def public_bytes(self) -> bytes:
return rust_x509.encode_extension_value(self)


class PrivateKeyUsagePeriod(ExtensionType):
oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD

def __init__(
self,
not_before: datetime.datetime | None = None,
not_after: datetime.datetime | None = None,
) -> None:
if not isinstance(not_before, datetime.datetime) and not_before is not None:
raise TypeError(
"not_before must be a datetime.datetime or None"
)

if not isinstance(not_after, datetime.datetime) and not_after is not None:
raise TypeError(
"not_after must be a datetime.datetime or None"
)

if not_before is None and not_after is None:
raise ValueError(
"At least one of not_before and not_after must not be None"
)

if not_before is not None and not_after is not None and not_before > not_after:
raise ValueError(
"not_before must be before not_after"
)

self._not_before = not_before
self._not_after = not_after

@property
def not_before(self) -> datetime.datetime | None:
return self._not_before

@property
def not_after(self) -> datetime.datetime | None:
return self._not_after

def __repr__(self) -> str:
return (
f"<PrivateKeyUsagePeriod(not_before={self.not_before}, "
f"not_after={self.not_after})>"
)

def __eq__(self, other: object) -> bool:
if not isinstance(other, PrivateKeyUsagePeriod):
return NotImplemented

return self.not_before == other.not_before and self.not_after == other.not_after

def __hash__(self) -> int:
return hash(
(
self.not_before,
self.not_after
)
)

def public_bytes(self) -> bytes:
return rust_x509.encode_extension_value(self)

class NameConstraints(ExtensionType):
oid = ExtensionOID.NAME_CONSTRAINTS

Expand Down
2 changes: 1 addition & 1 deletion src/rust/src/x509/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use cryptography_x509::extensions::{
DistributionPointName, DuplicateExtensionsError, IssuerAlternativeName, KeyUsage,
MSCertificateTemplate, NameConstraints, PolicyConstraints, PolicyInformation,
PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions,
SequenceOfSubtrees, UserNotice,
SequenceOfSubtrees, UserNotice
};
use cryptography_x509::extensions::{Extension, SubjectAlternativeName};
use cryptography_x509::{common, oid};
Expand Down
4 changes: 4 additions & 0 deletions tests/x509/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -4250,6 +4250,10 @@ def test_build_cert_with_rsa_key_too_small(
encipher_only=False,
decipher_only=False,
),
x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2002, 1, 1, 12, 1),
not_after=datetime.datetime(2030, 12, 31, 8, 30),
),
x509.OCSPNoCheck(),
x509.SubjectKeyIdentifier,
],
Expand Down
60 changes: 60 additions & 0 deletions tests/x509/test_x509_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,65 @@ def test_key_cert_sign_crl_sign(self, backend):
assert ku.crl_sign is True


class TestPrivateKeyUsagePeriodExtension:
def test_not_validity(self):
with pytest.raises(TypeError):
x509.PrivateKeyUsagePeriod("notvalidity") # type:ignore[arg-type]

def test_repr(self):
period = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2013, 1, 1),
)
ext = x509.Extension(
ExtensionOID.PRIVATE_KEY_USAGE_PERIOD, False, period
)
assert repr(ext) == (
"<Extension(oid=<ObjectIdentifier(oid=2.5.29.16, name=privateKeyUsagePeriod)>, "
"critical=False, value=<PrivateKeyUsagePeriod(not_before=2012-01-01 00:00:00, "
"not_after=2013-01-01 00:00:00)>)>"
)

def test_eq(self):
period = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2013, 1, 1),
)
period2 = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2013, 1, 1),
)
assert period == period2

def test_ne(self):
period = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2013, 1, 1),
)
period2 = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2014, 1, 1),
)
assert period != period2
assert period != object()

def test_hash(self):
period = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2013, 1, 1),
)
period2 = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2013, 1, 1),
)
period3 = x509.PrivateKeyUsagePeriod(
not_before=datetime.datetime(2012, 1, 1),
not_after=datetime.datetime(2014, 1, 1),
)
assert hash(period) == hash(period2)
assert hash(period) != hash(period3)


class TestDNSName:
def test_non_a_label(self):
with pytest.raises(ValueError):
Expand Down Expand Up @@ -6320,6 +6379,7 @@ def test_all_extension_oid_members_have_names_defined():
for oid in dir(ExtensionOID):
if oid.startswith("__"):
continue
print(getattr(ExtensionOID, oid))
assert getattr(ExtensionOID, oid) in _OID_NAMES


Expand Down
Loading