Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Changelog

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

* Support serialization of certificate signing requests using the
``public_bytes`` method of
:class:`~cryptography.x509.CertificateSigningRequest`.

0.9 - 2015-05-13
~~~~~~~~~~~~~~~~

Expand Down
11 changes: 11 additions & 0 deletions docs/x509.rst
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,17 @@ X.509 CSR (Certificate Signing Request) Object
>>> isinstance(csr.signature_hash_algorithm, hashes.SHA1)
True

.. method:: public_bytes(encoding)

:param encoding: The
:class:`~cryptography.hazmat.primitives.serialization.Encoding`
that will be used to serialize the certificate request.

:return bytes: The data that can be written to a file or sent
over the network to be signed by the certificate
authority.


.. class:: Name

.. versionadded:: 0.8
Expand Down
16 changes: 15 additions & 1 deletion src/cryptography/hazmat/backends/openssl/x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import hashes, serialization


def _obj2txt(backend, obj):
Expand Down Expand Up @@ -689,3 +689,17 @@ def extensions(self):
extensions.append(x509.Extension(oid, critical, value))

return x509.Extensions(extensions)

def public_bytes(self, encoding):
if not isinstance(encoding, serialization.Encoding):
raise TypeError("encoding must be an item from the Encoding enum")

bio = self._backend._create_mem_bio()
if encoding is serialization.Encoding.PEM:
res = self._backend._lib.PEM_write_bio_X509_REQ(
bio, self._x509_req
)
elif encoding is serialization.Encoding.DER:
res = self._backend._lib.i2d_X509_REQ_bio(bio, self._x509_req)
assert res == 1
return self._backend._read_mem_bio(bio)
6 changes: 6 additions & 0 deletions src/cryptography/x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,3 +1190,9 @@ def extensions(self):
"""
Returns the extensions in the signing request.
"""

@abc.abstractmethod
def public_bytes(self, encoding):
"""
Encodes the request to PEM or DER format.
"""
90 changes: 89 additions & 1 deletion tests/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from cryptography.hazmat.backends.interfaces import (
DSABackend, EllipticCurveBackend, RSABackend, X509Backend
)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa

from .hazmat.primitives.test_ec import _skip_curve_unsupported
Expand Down Expand Up @@ -471,6 +471,94 @@ def test_request_basic_constraints(self, backend):
),
]

def test_public_bytes_pem(self, backend):
# Load an existing CSR.
request = _load_cert(
os.path.join("x509", "requests", "rsa_sha1.pem"),
x509.load_pem_x509_csr,
backend
)

# Encode it to PEM and load it back.
request = x509.load_pem_x509_csr(request.public_bytes(
encoding=serialization.Encoding.PEM,
), backend)

# We should recover what we had to start with.
assert isinstance(request.signature_hash_algorithm, hashes.SHA1)
public_key = request.public_key()
assert isinstance(public_key, rsa.RSAPublicKey)
subject = request.subject
assert isinstance(subject, x509.Name)
assert list(subject) == [
x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'),
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'),
x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'),
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'),
x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'),
]

def test_public_bytes_der(self, backend):
# Load an existing CSR.
request = _load_cert(
os.path.join("x509", "requests", "rsa_sha1.pem"),
x509.load_pem_x509_csr,
backend
)

# Encode it to DER and load it back.
request = x509.load_der_x509_csr(request.public_bytes(
encoding=serialization.Encoding.DER,
), backend)

# We should recover what we had to start with.
assert isinstance(request.signature_hash_algorithm, hashes.SHA1)
public_key = request.public_key()
assert isinstance(public_key, rsa.RSAPublicKey)
subject = request.subject
assert isinstance(subject, x509.Name)
assert list(subject) == [
x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'),
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'),
x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'),
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'),
x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'),
]

def test_public_bytes_invalid_encoding(self, backend):
request = _load_cert(
os.path.join("x509", "requests", "rsa_sha1.pem"),
x509.load_pem_x509_csr,
backend
)

with pytest.raises(TypeError):
request.public_bytes('NotAnEncoding')

@pytest.mark.parametrize(
("request_path", "loader_func", "encoding"),
[
(
os.path.join("x509", "requests", "rsa_sha1.pem"),
x509.load_pem_x509_csr,
serialization.Encoding.PEM,
),
(
os.path.join("x509", "requests", "rsa_sha1.der"),
x509.load_der_x509_csr,
serialization.Encoding.DER,
),
]
)
def test_public_bytes_match(self, request_path, loader_func, encoding,
backend):
request_bytes = load_vectors_from_file(
request_path, lambda pemfile: pemfile.read(), mode="rb"
)
request = loader_func(request_bytes, backend)
serialized = request.public_bytes(encoding)
assert serialized == request_bytes


@pytest.mark.requires_backend_interface(interface=DSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
Expand Down