Skip to content

Commit

Permalink
Allow to serialize extension values as DER bytes strings (#6346)
Browse files Browse the repository at this point in the history
* Allow to serialize extension values as DER bytes string.

* Prepare test for SignedCertificateTimestamps.
  • Loading branch information
felixfontein authored Nov 12, 2021
1 parent 3225358 commit 19da50e
Show file tree
Hide file tree
Showing 6 changed files with 557 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Changelog
:class:`~cryptography.x509.CertificateBuilder`. These key types must be
signed with a different signing algorithm as ``X25519`` and ``X448`` do
not support signing.
* Extension values can now be serialized to a DER byte string by calling
:func:`~cryptography.x509.ExtensionType.public_bytes`.

.. _v35-0-0:

Expand Down
8 changes: 8 additions & 0 deletions docs/x509/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,14 @@ X.509 Extensions

Returns the OID associated with the given extension type.

.. method:: public_bytes()

.. versionadded:: 36.0

:return bytes:

A bytes string representing the extension's DER encoded value.

.. class:: KeyUsage(digital_signature, content_commitment, key_encipherment, data_encipherment, key_agreement, key_cert_sign, crl_sign, encipher_only, decipher_only)

.. versionadded:: 0.9
Expand Down
1 change: 1 addition & 0 deletions src/cryptography/hazmat/bindings/_rust/x509.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def load_der_x509_crl(data: bytes) -> x509.CertificateRevocationList: ...
def load_pem_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ...
def load_der_x509_csr(data: bytes) -> x509.CertificateSigningRequest: ...
def encode_name_bytes(name: x509.Name) -> bytes: ...
def encode_extension_value(extension: x509.ExtensionType) -> bytes: ...
def create_x509_certificate(
builder: x509.CertificateBuilder,
private_key: PRIVATE_KEY_TYPES,
Expand Down
95 changes: 95 additions & 0 deletions src/cryptography/x509/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from cryptography import utils
from cryptography.hazmat.bindings._rust import asn1
from cryptography.hazmat.bindings._rust import x509 as rust_x509
from cryptography.hazmat.primitives import constant_time, serialization
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
Expand Down Expand Up @@ -90,6 +91,16 @@ def __init__(self, msg: str, oid: ObjectIdentifier) -> None:
class ExtensionType(metaclass=abc.ABCMeta):
oid: typing.ClassVar[ObjectIdentifier]

def public_bytes(self) -> bytes:
"""
Serializes the extension type to DER.
"""
raise NotImplementedError(
"public_bytes is not implemented for extension type {0!r}".format(
self
)
)


class Extensions(object):
def __init__(
Expand Down Expand Up @@ -158,6 +169,9 @@ def __repr__(self) -> str:
def crl_number(self) -> int:
return self._crl_number

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


class AuthorityKeyIdentifier(ExtensionType):
oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER
Expand Down Expand Up @@ -261,6 +275,9 @@ def authority_cert_issuer(
def authority_cert_serial_number(self) -> typing.Optional[int]:
return self._authority_cert_serial_number

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


class SubjectKeyIdentifier(ExtensionType):
oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER
Expand Down Expand Up @@ -297,6 +314,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(self.digest)

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


class AuthorityInformationAccess(ExtensionType):
oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS
Expand Down Expand Up @@ -330,6 +350,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._descriptions))

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


class SubjectInformationAccess(ExtensionType):
oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS
Expand Down Expand Up @@ -363,6 +386,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._descriptions))

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


class AccessDescription(object):
def __init__(
Expand Down Expand Up @@ -452,6 +478,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash((self.ca, self.path_length))

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


class DeltaCRLIndicator(ExtensionType):
oid = ExtensionOID.DELTA_CRL_INDICATOR
Expand Down Expand Up @@ -481,6 +510,9 @@ def __hash__(self) -> int:
def __repr__(self) -> str:
return "<DeltaCRLIndicator(crl_number={0.crl_number})>".format(self)

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


class CRLDistributionPoints(ExtensionType):
oid = ExtensionOID.CRL_DISTRIBUTION_POINTS
Expand Down Expand Up @@ -518,6 +550,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._distribution_points))

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


class FreshestCRL(ExtensionType):
oid = ExtensionOID.FRESHEST_CRL
Expand Down Expand Up @@ -555,6 +590,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._distribution_points))

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


class DistributionPoint(object):
def __init__(
Expand Down Expand Up @@ -772,6 +810,9 @@ def require_explicit_policy(self) -> typing.Optional[int]:
def inhibit_policy_mapping(self) -> typing.Optional[int]:
return self._inhibit_policy_mapping

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


class CertificatePolicies(ExtensionType):
oid = ExtensionOID.CERTIFICATE_POLICIES
Expand Down Expand Up @@ -803,6 +844,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._policies))

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


class PolicyInformation(object):
def __init__(
Expand Down Expand Up @@ -986,6 +1030,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._usages))

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


class OCSPNoCheck(ExtensionType):
oid = ExtensionOID.OCSP_NO_CHECK
Expand All @@ -1005,6 +1052,9 @@ def __hash__(self) -> int:
def __repr__(self) -> str:
return "<OCSPNoCheck()>"

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


class PrecertPoison(ExtensionType):
oid = ExtensionOID.PRECERT_POISON
Expand All @@ -1024,6 +1074,9 @@ def __hash__(self) -> int:
def __repr__(self) -> str:
return "<PrecertPoison()>"

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


class TLSFeature(ExtensionType):
oid = ExtensionOID.TLS_FEATURE
Expand Down Expand Up @@ -1058,6 +1111,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(tuple(self._features))

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


class TLSFeatureType(utils.Enum):
# status_request is defined in RFC 6066 and is used for what is commonly
Expand Down Expand Up @@ -1104,6 +1160,9 @@ def __hash__(self) -> int:
def skip_certs(self) -> int:
return self._skip_certs

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


class KeyUsage(ExtensionType):
oid = ExtensionOID.KEY_USAGE
Expand Down Expand Up @@ -1237,6 +1296,9 @@ def __hash__(self) -> int:
)
)

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


class NameConstraints(ExtensionType):
oid = ExtensionOID.NAME_CONSTRAINTS
Expand Down Expand Up @@ -1335,6 +1397,9 @@ def excluded_subtrees(
) -> typing.Optional[typing.List[GeneralName]]:
return self._excluded_subtrees

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


class Extension(typing.Generic[ExtensionTypeVar]):
def __init__(
Expand Down Expand Up @@ -1559,6 +1624,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(self._general_names)

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


class IssuerAlternativeName(ExtensionType):
oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME
Expand Down Expand Up @@ -1640,6 +1708,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(self._general_names)

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


class CertificateIssuer(ExtensionType):
oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER
Expand Down Expand Up @@ -1721,6 +1792,9 @@ def __ne__(self, other: typing.Any) -> bool:
def __hash__(self) -> int:
return hash(self._general_names)

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


class CRLReason(ExtensionType):
oid = CRLEntryExtensionOID.CRL_REASON
Expand Down Expand Up @@ -1750,6 +1824,9 @@ def __hash__(self) -> int:
def reason(self) -> ReasonFlags:
return self._reason

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


class InvalidityDate(ExtensionType):
oid = CRLEntryExtensionOID.INVALIDITY_DATE
Expand Down Expand Up @@ -1781,6 +1858,9 @@ def __hash__(self) -> int:
def invalidity_date(self) -> datetime.datetime:
return self._invalidity_date

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


class PrecertificateSignedCertificateTimestamps(ExtensionType):
oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS
Expand Down Expand Up @@ -1826,6 +1906,9 @@ def __eq__(self, other: typing.Any) -> bool:
def __ne__(self, other: typing.Any) -> bool:
return not self == other

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


class SignedCertificateTimestamps(ExtensionType):
oid = ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS
Expand Down Expand Up @@ -1869,6 +1952,9 @@ def __eq__(self, other: typing.Any) -> bool:
def __ne__(self, other: typing.Any) -> bool:
return not self == other

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


class OCSPNonce(ExtensionType):
oid = OCSPExtensionOID.NONCE
Expand Down Expand Up @@ -1898,6 +1984,9 @@ def __repr__(self) -> str:
def nonce(self) -> bytes:
return self._nonce

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


class IssuingDistributionPoint(ExtensionType):
oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT
Expand Down Expand Up @@ -2058,6 +2147,9 @@ def indirect_crl(self) -> bool:
def only_contains_attribute_certs(self) -> bool:
return self._only_contains_attribute_certs

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


class UnrecognizedExtension(ExtensionType):
def __init__(self, oid: ObjectIdentifier, value: bytes) -> None:
Expand Down Expand Up @@ -2091,3 +2183,6 @@ def __ne__(self, other: typing.Any) -> bool:

def __hash__(self) -> int:
return hash((self.oid, self.value))

def public_bytes(self) -> bytes:
return self.value
27 changes: 27 additions & 0 deletions src/rust/src/x509/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// for complete details.

use crate::asn1::PyAsn1Error;
use crate::x509;
use chrono::{Datelike, TimeZone, Timelike};
use pyo3::ToPyObject;
use std::collections::HashSet;
Expand Down Expand Up @@ -639,6 +640,31 @@ pub(crate) fn encode_extensions<
)))
}

#[pyo3::prelude::pyfunction]
fn encode_extension_value<'p>(
py: pyo3::Python<'p>,
py_ext: &'p pyo3::PyAny,
) -> pyo3::PyResult<&'p pyo3::types::PyBytes> {
let oid = asn1::ObjectIdentifier::from_string(
py_ext
.getattr("oid")?
.getattr("dotted_string")?
.extract::<&str>()?,
)
.unwrap();

if let Some(data) = x509::extensions::encode_extension(&oid, py_ext)? {
// TODO: extra copy
let py_data = pyo3::types::PyBytes::new(py, &data);
return Ok(py_data);
}

return Err(pyo3::exceptions::PyNotImplementedError::new_err(format!(
"Extension not supported: {}",
oid
)));
}

pub(crate) fn chrono_to_py<'p>(
py: pyo3::Python<'p>,
dt: &chrono::DateTime<chrono::Utc>,
Expand Down Expand Up @@ -736,6 +762,7 @@ mod tests {
}

pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> {
module.add_wrapped(pyo3::wrap_pyfunction!(encode_extension_value))?;
module.add_wrapped(pyo3::wrap_pyfunction!(encode_name_bytes))?;

Ok(())
Expand Down
Loading

0 comments on commit 19da50e

Please sign in to comment.