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

PKCS12: return 'friendly name' with PKCS12KeyAndCertificates API #6348

Merged
merged 13 commits into from
Oct 6, 2021
Merged
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Changelog

.. note:: This version is not yet released and is under active development.

* Added support for parsing PKCS12 files with friendly names for all
certificates with
:func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_pkcs12`,
which will return an object of type
:class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates`.

.. _v35-0-0:

35.0.0 - 2021-09-29
Expand Down
60 changes: 60 additions & 0 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,27 @@ file suffix.
``additional_certificates`` is a list of all other
:class:`~cryptography.x509.Certificate` instances in the PKCS12 object.

.. function:: load_pkcs12(data, password, backend=None)

.. versionadded:: 36.0

Deserialize a PKCS12 blob, and return a
:class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates`
instance.

:param data: The binary data.
:type data: :term:`bytes-like`

:param password: The password to use to decrypt the data. ``None``
if the PKCS12 is not encrypted.
:type password: :term:`bytes-like`

:param backend: An optional backend instance.

:returns: A
:class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates`
instance.

.. function:: serialize_key_and_certificates(name, key, cert, cas, encryption_algorithm)

.. versionadded:: 3.0
Expand Down Expand Up @@ -543,6 +564,45 @@ file suffix.

:return bytes: Serialized PKCS12.

.. class:: PKCS12Certificate

.. versionadded:: 36.0

Represents additional data provided for a certificate in a PKCS12 file.

.. attribute:: certificate

A :class:`~cryptography.x509.Certificate` instance.

.. attribute:: friendly_name

:type: bytes
felixfontein marked this conversation as resolved.
Show resolved Hide resolved

An optional byte string containing the friendly name of the certificate.

.. class:: PKCS12KeyAndCertificates

.. versionadded:: 36.0

A simplified representation of a PKCS12 file.

.. attribute:: key

An optional private key belonging to
:attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.cert`.

.. attribute:: cert

An optional
:class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate`
instance belonging to the private key
:attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12KeyAndCertificates.key`.

.. attribute:: additional_certs

A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate`
instances.

PKCS7
~~~~~

Expand Down
6 changes: 6 additions & 0 deletions src/cryptography/hazmat/backends/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,12 @@ def load_key_and_certificates_from_pkcs12(self, data, password):
Returns a tuple of (key, cert, [certs])
"""

@abc.abstractmethod
def load_pkcs12(self, data, password):
"""
Returns a PKCS12KeyAndCertificates object
"""

@abc.abstractmethod
def serialize_key_and_certificates_to_pkcs12(
self, name, key, cert, cas, encryption_algorithm
Expand Down
29 changes: 26 additions & 3 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
)
from cryptography.hazmat.primitives.kdf import scrypt
from cryptography.hazmat.primitives.serialization import pkcs7, ssh
from cryptography.hazmat.primitives.serialization.pkcs12 import (
PKCS12Certificate,
PKCS12KeyAndCertificates,
)
from cryptography.x509 import ocsp
from cryptography.x509.base import PUBLIC_KEY_TYPES
from cryptography.x509.name import Name
Expand Down Expand Up @@ -2499,6 +2503,14 @@ def _zeroed_null_terminated_buf(self, data):
self._zero_data(self._ffi.cast("uint8_t *", buf), data_len)

def load_key_and_certificates_from_pkcs12(self, data, password):
pkcs12 = self.load_pkcs12(data, password)
return (
pkcs12.key,
pkcs12.cert.certificate if pkcs12.cert else None,
[cert.certificate for cert in pkcs12.additional_certs],
)

def load_pkcs12(self, data, password):
if password is not None:
utils._check_byteslike("password", password)

Expand Down Expand Up @@ -2537,7 +2549,12 @@ def load_key_and_certificates_from_pkcs12(self, data, password):

if x509_ptr[0] != self._ffi.NULL:
x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free)
cert = self._ossl2cert(x509)
cert_obj = self._ossl2cert(x509)
name = None
maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL)
if maybe_name != self._ffi.NULL:
name = self._ffi.string(maybe_name)
cert = PKCS12Certificate(cert_obj, name)

if sk_x509_ptr[0] != self._ffi.NULL:
sk_x509 = self._ffi.gc(sk_x509_ptr[0], self._lib.sk_X509_free)
Expand All @@ -2556,9 +2573,15 @@ def load_key_and_certificates_from_pkcs12(self, data, password):
self.openssl_assert(x509 != self._ffi.NULL)
x509 = self._ffi.gc(x509, self._lib.X509_free)
addl_cert = self._ossl2cert(x509)
additional_certificates.append(addl_cert)
addl_name = None
maybe_name = self._lib.X509_alias_get0(x509, self._ffi.NULL)
if maybe_name != self._ffi.NULL:
addl_name = self._ffi.string(maybe_name)
additional_certificates.append(
PKCS12Certificate(addl_cert, addl_name)
)

return (key, cert, additional_certificates)
return PKCS12KeyAndCertificates(key, cert, additional_certificates)

def serialize_key_and_certificates_to_pkcs12(
self, name, key, cert, cas, encryption_algorithm
Expand Down
117 changes: 117 additions & 0 deletions src/cryptography/hazmat/primitives/serialization/pkcs12.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,114 @@
]


class PKCS12Certificate:
def __init__(
self,
cert: x509.Certificate,
friendly_name: typing.Optional[bytes],
):
if not isinstance(cert, x509.Certificate):
raise TypeError("Expecting x509.Certificate object")
if friendly_name is not None and not isinstance(friendly_name, bytes):
raise TypeError("friendly_name must be bytes")
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
self._cert = cert
self._friendly_name = friendly_name

@property
def friendly_name(self) -> typing.Optional[bytes]:
return self._friendly_name

@property
def certificate(self) -> x509.Certificate:
return self._cert

def __eq__(self, other: typing.Any) -> bool:
if not isinstance(other, PKCS12Certificate):
return NotImplemented

return (
self.certificate == other.certificate
and self.friendly_name == other.friendly_name
)

def __ne__(self, other: typing.Any) -> bool:
return not self == other

def __hash__(self) -> int:
return hash((self.certificate, self.friendly_name))

def __repr__(self) -> str:
return "<PKCS12Certificate({}, friendly_name={!r})>".format(
self.certificate, self.friendly_name
)


class PKCS12KeyAndCertificates:
def __init__(
self,
key: typing.Optional[_ALLOWED_PKCS12_TYPES],
cert: typing.Optional[PKCS12Certificate],
additional_certs: typing.List[PKCS12Certificate],
):
if key is not None and not isinstance(
key,
(
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
),
):
raise TypeError(
"Key must be RSA, DSA, or EllipticCurve private key."
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
)
if cert is not None and not isinstance(cert, PKCS12Certificate):
raise TypeError("cert must be a PKCS12Certificate object")
if not all(
isinstance(add_cert, PKCS12Certificate)
for add_cert in additional_certs
):
raise TypeError(
"all values in additional_certs must be PKCS12Certificate objects"
)
self._key = key
self._cert = cert
self._additional_certs = additional_certs

@property
def key(self) -> typing.Optional[_ALLOWED_PKCS12_TYPES]:
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
return self._key

@property
def cert(self) -> typing.Optional[PKCS12Certificate]:
return self._cert

@property
def additional_certs(self) -> typing.List[PKCS12Certificate]:
return self._additional_certs

def __eq__(self, other: typing.Any) -> bool:
if not isinstance(other, PKCS12KeyAndCertificates):
return NotImplemented

return (
self.key == other.key
and self.cert == other.cert
and self.additional_certs == other.additional_certs
)

def __ne__(self, other: typing.Any) -> bool:
return not self == other

def __hash__(self) -> int:
return hash((self.key, self.cert, tuple(self.additional_certs)))

def __repr__(self) -> str:
fmt = (
"<PKCS12KeyAndCertificates(key={}, cert={}, additional_certs={})>"
)
return fmt.format(self.key, self.cert, self.additional_certs)


def load_key_and_certificates(
data: bytes,
password: typing.Optional[bytes],
Expand All @@ -31,6 +139,15 @@ def load_key_and_certificates(
return backend.load_key_and_certificates_from_pkcs12(data, password)


def load_pkcs12(
data: bytes,
password: typing.Optional[bytes],
backend: typing.Optional[Backend] = None,
) -> PKCS12KeyAndCertificates:
backend = _get_backend(backend)
return backend.load_pkcs12(data, password)


def serialize_key_and_certificates(
name: typing.Optional[bytes],
key: typing.Optional[_ALLOWED_PKCS12_TYPES],
Expand Down
Loading