aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2018-11-29 11:51:38 +0800
committerAlex Gaynor <alex.gaynor@gmail.com>2018-11-28 22:51:38 -0500
commite4e7b89fb627b372cde4158ceb7078d8769497cb (patch)
tree9dc87beda0cf2d1a948feea01c87361feb1a32af
parent2f2f3d2e414a0167ae3a98b9b608904b2c76a35f (diff)
downloadcryptography-e4e7b89fb627b372cde4158ceb7078d8769497cb.tar.gz
cryptography-e4e7b89fb627b372cde4158ceb7078d8769497cb.tar.bz2
cryptography-e4e7b89fb627b372cde4158ceb7078d8769497cb.zip
PKCS12 Basic Parsing (#4553)
* PKCS12 parsing support * running all the tests is so gauche * rename func * various significant fixes * dangerous idiot here * move pkcs12 * docs updates * a bit more prose
-rw-r--r--CHANGELOG.rst2
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst37
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py46
-rw-r--r--src/cryptography/hazmat/primitives/serialization/pkcs12.py9
-rw-r--r--tests/hazmat/backends/test_openssl_memleak.py18
-rw-r--r--tests/hazmat/primitives/test_pkcs12.py110
6 files changed, 222 insertions, 0 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6f2c964a..0cc468c5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -16,6 +16,8 @@ Changelog
:class:`~cryptography.hazmat.primitives.hashes.SHA3_384`, and
:class:`~cryptography.hazmat.primitives.hashes.SHA3_512` when using OpenSSL
1.1.1.
+* Added initial support for parsing PKCS12 files with
+ :func:`~cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates`.
.. _v2-4-2:
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index 90ec10eb..7b3fb1d6 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -397,9 +397,46 @@ DSA keys look almost identical but begin with ``ssh-dss`` rather than
:raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized
key is of a type that is not supported.
+PKCS12
+~~~~~~
+
+.. currentmodule:: cryptography.hazmat.primitives.serialization.pkcs12
+
+PKCS12 is a binary format described in :rfc:`7292`. It can contain
+certificates, keys, and more. PKCS12 files commonly have a ``pfx`` or ``p12``
+file suffix.
+
+.. note::
+
+ ``cryptography`` only supports a single private key and associated
+ certificates when parsing PKCS12 files at this time.
+
+.. function:: load_key_and_certificates(data, password, backend)
+
+ .. versionadded:: 2.5
+
+ Deserialize a PKCS12 blob.
+
+ :param bytes data: The binary data.
+
+ :param bytes password: The password to use to decrypt the data. ``None``
+ if the PKCS12 is not encrypted.
+
+ :param backend: A backend instance.
+
+ :returns: A tuple of
+ ``(private_key, certificate, additional_certificates)``.
+ ``private_key`` is a private key type or ``None``, ``certificate``
+ is either the :class:`~cryptography.x509.Certificate` whose public key
+ matches the private key in the PKCS 12 object or ``None``, and
+ ``additional_certificates`` is a list of all other
+ :class:`~cryptography.x509.Certificate` instances in the PKCS12 object.
+
Serialization Formats
~~~~~~~~~~~~~~~~~~~~~
+.. currentmodule:: cryptography.hazmat.primitives.serialization
+
.. class:: PrivateFormat
.. versionadded:: 0.8
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 44c2e3cd..5a22a555 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -2129,6 +2129,52 @@ class Backend(object):
self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL
)
+ def load_key_and_certificates_from_pkcs12(self, data, password):
+ if password is None:
+ password = self._ffi.NULL
+ elif not isinstance(password, bytes):
+ raise TypeError("Password must be a byte string or None")
+
+ bio = self._bytes_to_bio(data)
+ p12 = self._lib.d2i_PKCS12_bio(bio.bio, self._ffi.NULL)
+ if p12 == self._ffi.NULL:
+ self._consume_errors()
+ raise ValueError("Could not deserialize PKCS12 data")
+
+ p12 = self._ffi.gc(p12, self._lib.PKCS12_free)
+ evp_pkey_ptr = self._ffi.new("EVP_PKEY **")
+ x509_ptr = self._ffi.new("X509 **")
+ sk_x509_ptr = self._ffi.new("Cryptography_STACK_OF_X509 **")
+ res = self._lib.PKCS12_parse(
+ p12, password, evp_pkey_ptr, x509_ptr, sk_x509_ptr
+ )
+ if res == 0:
+ self._consume_errors()
+ raise ValueError("Invalid password or PKCS12 data")
+
+ cert = None
+ key = None
+ additional_certificates = []
+
+ if evp_pkey_ptr[0] != self._ffi.NULL:
+ evp_pkey = self._ffi.gc(evp_pkey_ptr[0], self._lib.EVP_PKEY_free)
+ key = self._evp_pkey_to_private_key(evp_pkey)
+
+ if x509_ptr[0] != self._ffi.NULL:
+ x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free)
+ cert = _Certificate(self, x509)
+
+ if sk_x509_ptr[0] != self._ffi.NULL:
+ sk_x509 = self._ffi.gc(sk_x509_ptr[0], self._lib.sk_X509_free)
+ num = self._lib.sk_X509_num(sk_x509_ptr[0])
+ for i in range(num):
+ x509 = self._lib.sk_X509_value(sk_x509, i)
+ x509 = self._ffi.gc(x509, self._lib.X509_free)
+ self.openssl_assert(x509 != self._ffi.NULL)
+ additional_certificates.append(_Certificate(self, x509))
+
+ return (key, cert, additional_certificates)
+
class GetCipherByName(object):
def __init__(self, fmt):
diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs12.py b/src/cryptography/hazmat/primitives/serialization/pkcs12.py
new file mode 100644
index 00000000..98161d57
--- /dev/null
+++ b/src/cryptography/hazmat/primitives/serialization/pkcs12.py
@@ -0,0 +1,9 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+
+def load_key_and_certificates(data, password, backend):
+ return backend.load_key_and_certificates_from_pkcs12(data, password)
diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py
index 483387af..6f42ed79 100644
--- a/tests/hazmat/backends/test_openssl_memleak.py
+++ b/tests/hazmat/backends/test_openssl_memleak.py
@@ -307,3 +307,21 @@ class TestOpenSSLMemoryLeaks(object):
).add_extension(x509.OCSPNonce(b"0000"), False)
req = builder.build()
"""))
+
+ @pytest.mark.parametrize("path", [
+ "pkcs12/cert-aes256cbc-no-key.p12",
+ "pkcs12/cert-key-aes256cbc.p12",
+ ])
+ def test_load_pkcs12_key_and_certificates(self, path):
+ assert_no_memory_leaks(textwrap.dedent("""
+ def func(path):
+ from cryptography import x509
+ from cryptography.hazmat.backends.openssl import backend
+ from cryptography.hazmat.primitives.serialization import pkcs12
+ import cryptography_vectors
+
+ with cryptography_vectors.open_vector_file(path, "rb") as f:
+ pkcs12.load_key_and_certificates(
+ f.read(), b"cryptography", backend
+ )
+ """), [path])
diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py
new file mode 100644
index 00000000..85be3b51
--- /dev/null
+++ b/tests/hazmat/primitives/test_pkcs12.py
@@ -0,0 +1,110 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+import os
+
+import pytest
+
+from cryptography import x509
+from cryptography.hazmat.backends.interfaces import DERSerializationBackend
+from cryptography.hazmat.primitives.serialization import load_pem_private_key
+from cryptography.hazmat.primitives.serialization.pkcs12 import (
+ load_key_and_certificates
+)
+
+from .utils import load_vectors_from_file
+
+
+@pytest.mark.requires_backend_interface(interface=DERSerializationBackend)
+class TestPKCS12(object):
+ @pytest.mark.parametrize(
+ ("filename", "password"),
+ [
+ ("cert-key-aes256cbc.p12", b"cryptography"),
+ ("cert-none-key-none.p12", b"cryptography"),
+ ("cert-rc2-key-3des.p12", b"cryptography"),
+ ("no-password.p12", None),
+ ]
+ )
+ def test_load_pkcs12_ec_keys(self, filename, password, backend):
+ cert = load_vectors_from_file(
+ os.path.join("x509", "custom", "ca", "ca.pem"),
+ lambda pemfile: x509.load_pem_x509_certificate(
+ pemfile.read(), backend
+ ), mode="rb"
+ )
+ key = load_vectors_from_file(
+ os.path.join("x509", "custom", "ca", "ca_key.pem"),
+ lambda pemfile: load_pem_private_key(
+ pemfile.read(), None, backend
+ ), mode="rb"
+ )
+ parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file(
+ os.path.join("pkcs12", filename),
+ lambda derfile: load_key_and_certificates(
+ derfile.read(), password, backend
+ ), mode="rb"
+ )
+ assert parsed_cert == cert
+ assert parsed_key.private_numbers() == key.private_numbers()
+ assert parsed_more_certs == []
+
+ def test_load_pkcs12_cert_only(self, backend):
+ cert = load_vectors_from_file(
+ os.path.join("x509", "custom", "ca", "ca.pem"),
+ lambda pemfile: x509.load_pem_x509_certificate(
+ pemfile.read(), backend
+ ), mode="rb"
+ )
+ parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file(
+ os.path.join("pkcs12", "cert-aes256cbc-no-key.p12"),
+ lambda data: load_key_and_certificates(
+ data.read(), b"cryptography", backend
+ ),
+ mode="rb"
+ )
+ assert parsed_cert is None
+ assert parsed_key is None
+ assert parsed_more_certs == [cert]
+
+ def test_load_pkcs12_key_only(self, backend):
+ key = load_vectors_from_file(
+ os.path.join("x509", "custom", "ca", "ca_key.pem"),
+ lambda pemfile: load_pem_private_key(
+ pemfile.read(), None, backend
+ ), mode="rb"
+ )
+ parsed_key, parsed_cert, parsed_more_certs = load_vectors_from_file(
+ os.path.join("pkcs12", "no-cert-key-aes256cbc.p12"),
+ lambda data: load_key_and_certificates(
+ data.read(), b"cryptography", backend
+ ),
+ mode="rb"
+ )
+ assert parsed_key.private_numbers() == key.private_numbers()
+ assert parsed_cert is None
+ assert parsed_more_certs == []
+
+ def test_non_bytes(self, backend):
+ with pytest.raises(TypeError):
+ load_key_and_certificates(
+ b"irrelevant", object(), backend
+ )
+
+ def test_not_a_pkcs12(self, backend):
+ with pytest.raises(ValueError):
+ load_key_and_certificates(
+ b"invalid", b"pass", backend
+ )
+
+ def test_invalid_password(self, backend):
+ with pytest.raises(ValueError):
+ load_vectors_from_file(
+ os.path.join("pkcs12", "cert-key-aes256cbc.p12"),
+ lambda derfile: load_key_and_certificates(
+ derfile.read(), b"invalid", backend
+ ), mode="rb"
+ )