Skip to content

Commit b19fc3d

Browse files
Add Support for X509 extension - PrivateKeyUsagePeriod (pyca#12738)
* feat: add private key usage period extension * fix: resolve linter issues * test: increase coverage * test: increase coverage * test: increase coverage * test: add more vectors * feat: add implicit tagging * fix: replaced GeneralizedTime with X509GeneralizedTime * chore: remove test vectors * chore: update vector path * test: remove test for der file * Update tests/x509/test_x509_ext.py Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> * Update src/rust/src/x509/extensions.rs Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> * Update src/rust/src/x509/extensions.rs Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> * fix: assert on exact values * refactor: switch to ec key for better performance --------- Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com>
1 parent f556350 commit b19fc3d

File tree

10 files changed

+363
-3
lines changed

10 files changed

+363
-3
lines changed

src/cryptography/hazmat/_oid.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class ExtensionOID:
1414
SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9")
1515
SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14")
1616
KEY_USAGE = ObjectIdentifier("2.5.29.15")
17+
PRIVATE_KEY_USAGE_PERIOD = ObjectIdentifier("2.5.29.16")
1718
SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17")
1819
ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18")
1920
BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
@@ -305,6 +306,7 @@ class AttributeOID:
305306
ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes",
306307
ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier",
307308
ExtensionOID.KEY_USAGE: "keyUsage",
309+
ExtensionOID.PRIVATE_KEY_USAGE_PERIOD: "privateKeyUsagePeriod",
308310
ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName",
309311
ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName",
310312
ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints",

src/cryptography/x509/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
PolicyInformation,
6767
PrecertificateSignedCertificateTimestamps,
6868
PrecertPoison,
69+
PrivateKeyUsagePeriod,
6970
ProfessionInfo,
7071
ReasonFlags,
7172
SignedCertificateTimestamps,
@@ -115,6 +116,7 @@
115116
OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY
116117
OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME
117118
OID_KEY_USAGE = ExtensionOID.KEY_USAGE
119+
OID_PRIVATE_KEY_USAGE_PERIOD = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD
118120
OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS
119121
OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK
120122
OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS
@@ -233,6 +235,7 @@
233235
"PolicyInformation",
234236
"PrecertPoison",
235237
"PrecertificateSignedCertificateTimestamps",
238+
"PrivateKeyUsagePeriod",
236239
"ProfessionInfo",
237240
"PublicKeyAlgorithmOID",
238241
"RFC822Name",

src/cryptography/x509/extensions.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,71 @@ def public_bytes(self) -> bytes:
12661266
return rust_x509.encode_extension_value(self)
12671267

12681268

1269+
class PrivateKeyUsagePeriod(ExtensionType):
1270+
oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD
1271+
1272+
def __init__(
1273+
self,
1274+
not_before: datetime.datetime | None,
1275+
not_after: datetime.datetime | None,
1276+
) -> None:
1277+
if (
1278+
not isinstance(not_before, datetime.datetime)
1279+
and not_before is not None
1280+
):
1281+
raise TypeError("not_before must be a datetime.datetime or None")
1282+
1283+
if (
1284+
not isinstance(not_after, datetime.datetime)
1285+
and not_after is not None
1286+
):
1287+
raise TypeError("not_after must be a datetime.datetime or None")
1288+
1289+
if not_before is None and not_after is None:
1290+
raise ValueError(
1291+
"At least one of not_before and not_after must not be None"
1292+
)
1293+
1294+
if (
1295+
not_before is not None
1296+
and not_after is not None
1297+
and not_before > not_after
1298+
):
1299+
raise ValueError("not_before must be before not_after")
1300+
1301+
self._not_before = not_before
1302+
self._not_after = not_after
1303+
1304+
@property
1305+
def not_before(self) -> datetime.datetime | None:
1306+
return self._not_before
1307+
1308+
@property
1309+
def not_after(self) -> datetime.datetime | None:
1310+
return self._not_after
1311+
1312+
def __repr__(self) -> str:
1313+
return (
1314+
f"<PrivateKeyUsagePeriod(not_before={self.not_before}, "
1315+
f"not_after={self.not_after})>"
1316+
)
1317+
1318+
def __eq__(self, other: object) -> bool:
1319+
if not isinstance(other, PrivateKeyUsagePeriod):
1320+
return NotImplemented
1321+
1322+
return (
1323+
self.not_before == other.not_before
1324+
and self.not_after == other.not_after
1325+
)
1326+
1327+
def __hash__(self) -> int:
1328+
return hash((self.not_before, self.not_after))
1329+
1330+
def public_bytes(self) -> bytes:
1331+
return rust_x509.encode_extension_value(self)
1332+
1333+
12691334
class NameConstraints(ExtensionType):
12701335
oid = ExtensionOID.NAME_CONSTRAINTS
12711336

src/rust/cryptography-x509/src/extensions.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,14 @@ pub struct Admissions<'a, Op: Asn1Operation> {
301301
pub contents_of_admissions: Op::SequenceOfVec<'a, Admission<'a, Op>>,
302302
}
303303

304+
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
305+
pub struct PrivateKeyUsagePeriod {
306+
#[implicit(0)]
307+
pub not_before: Option<asn1::X509GeneralizedTime>,
308+
#[implicit(1)]
309+
pub not_after: Option<asn1::X509GeneralizedTime>,
310+
}
311+
304312
#[cfg(test)]
305313
mod tests {
306314
use super::{BasicConstraints, Extension, Extensions, KeyUsage};

src/rust/cryptography-x509/src/oid.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub const INHIBIT_ANY_POLICY_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29,
4545
pub const ACCEPTABLE_RESPONSES_OID: asn1::ObjectIdentifier =
4646
asn1::oid!(1, 3, 6, 1, 5, 5, 7, 48, 1, 4);
4747
pub const ADMISSIONS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 36, 8, 3, 3);
48+
pub const PRIVATE_KEY_USAGE_PERIOD_OID: asn1::ObjectIdentifier = asn1::oid!(2, 5, 29, 16);
4849

4950
// Public key identifiers
5051
pub const EC_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 2, 1);

src/rust/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ pub static SUBJECT_KEY_IDENTIFIER: LazyPyImport =
250250
pub static TLS_FEATURE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["TLSFeature"]);
251251
pub static SUBJECT_ALTERNATIVE_NAME: LazyPyImport =
252252
LazyPyImport::new("cryptography.x509", &["SubjectAlternativeName"]);
253+
pub static PRIVATE_KEY_USAGE_PERIOD: LazyPyImport =
254+
LazyPyImport::new("cryptography.x509", &["PrivateKeyUsagePeriod"]);
253255
pub static POLICY_INFORMATION: LazyPyImport =
254256
LazyPyImport::new("cryptography.x509", &["PolicyInformation"]);
255257
pub static USER_NOTICE: LazyPyImport = LazyPyImport::new("cryptography.x509", &["UserNotice"]);

src/rust/src/x509/certificate.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use cryptography_x509::extensions::{
1111
Admission, Admissions, AuthorityKeyIdentifier, BasicConstraints, DisplayText,
1212
DistributionPoint, DistributionPointName, DuplicateExtensionsError, ExtendedKeyUsage,
1313
Extension, IssuerAlternativeName, KeyUsage, MSCertificateTemplate, NameConstraints,
14-
NamingAuthority, PolicyConstraints, PolicyInformation, PolicyQualifierInfo, ProfessionInfo,
15-
Qualifier, RawExtensions, SequenceOfAccessDescriptions, SequenceOfSubtrees,
16-
SubjectAlternativeName, UserNotice,
14+
NamingAuthority, PolicyConstraints, PolicyInformation, PolicyQualifierInfo,
15+
PrivateKeyUsagePeriod, ProfessionInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions,
16+
SequenceOfSubtrees, SubjectAlternativeName, UserNotice,
1717
};
1818
use cryptography_x509::{common, oid};
1919
use cryptography_x509_verification::ops::CryptoOps;
@@ -946,6 +946,31 @@ pub fn parse_cert_ext<'p>(
946946
.call1((admission_authority, py_admissions))?,
947947
))
948948
}
949+
oid::PRIVATE_KEY_USAGE_PERIOD_OID => {
950+
let pkup = ext.value::<PrivateKeyUsagePeriod>()?;
951+
952+
let not_before = match &pkup.not_before {
953+
Some(t) => {
954+
let dt = t.as_datetime();
955+
Some(x509::datetime_to_py(py, dt)?)
956+
}
957+
None => None,
958+
};
959+
960+
let not_after = match &pkup.not_after {
961+
Some(t) => {
962+
let dt = t.as_datetime();
963+
Some(x509::datetime_to_py(py, dt)?)
964+
}
965+
None => None,
966+
};
967+
968+
Ok(Some(
969+
types::PRIVATE_KEY_USAGE_PERIOD
970+
.get(py)?
971+
.call1((not_before, not_after))?,
972+
))
973+
}
949974
_ => Ok(None),
950975
}
951976
}

src/rust/src/x509/extensions.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,39 @@ pub(crate) fn encode_extension(
728728
};
729729
Ok(Some(asn1::write_single(&admission)?))
730730
}
731+
&oid::PRIVATE_KEY_USAGE_PERIOD_OID => {
732+
let der = encode_private_key_usage_period(py, ext)?;
733+
Ok(Some(der))
734+
}
731735
_ => Ok(None),
732736
}
733737
}
738+
739+
pub(crate) fn encode_private_key_usage_period(
740+
py: pyo3::Python<'_>,
741+
ext: &pyo3::Bound<'_, pyo3::PyAny>,
742+
) -> CryptographyResult<Vec<u8>> {
743+
let not_before = ext.getattr(pyo3::intern!(py, "not_before"))?;
744+
let not_after = ext.getattr(pyo3::intern!(py, "not_after"))?;
745+
746+
let not_before_value = if !not_before.is_none() {
747+
let dt = x509::py_to_datetime(py, not_before)?;
748+
Some(asn1::X509GeneralizedTime::new(dt)?)
749+
} else {
750+
None
751+
};
752+
753+
let not_after_value = if !not_after.is_none() {
754+
let dt = x509::py_to_datetime(py, not_after)?;
755+
Some(asn1::X509GeneralizedTime::new(dt)?)
756+
} else {
757+
None
758+
};
759+
760+
let pkup = extensions::PrivateKeyUsagePeriod {
761+
not_before: not_before_value,
762+
not_after: not_after_value,
763+
};
764+
765+
Ok(asn1::write_single(&pkup)?)
766+
}

tests/x509/test_x509.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4383,6 +4383,10 @@ def test_build_cert_with_rsa_key_too_small(
43834383
encipher_only=False,
43844384
decipher_only=False,
43854385
),
4386+
x509.PrivateKeyUsagePeriod(
4387+
not_before=datetime.datetime(2002, 1, 1, 12, 1),
4388+
not_after=datetime.datetime(2030, 12, 31, 8, 30),
4389+
),
43864390
x509.OCSPNoCheck(),
43874391
x509.SubjectKeyIdentifier,
43884392
],

0 commit comments

Comments
 (0)