mirror of
https://github.com/saymrwulf/cryptography.git
synced 2026-05-14 20:37:55 +00:00
Merge pull request #2045 from sigmavirus24/csr-builder
Adds CSR Builder (Redux of #1927)
This commit is contained in:
commit
8f768dc5b9
9 changed files with 543 additions and 3 deletions
|
|
@ -24,6 +24,8 @@ Changelog
|
|||
and :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHMAC`.
|
||||
* Raise a ``TypeError`` when passing objects that are not text as the value to
|
||||
:class:`~cryptography.x509.NameAttribute`.
|
||||
* Add support for creating certificate signing requests with
|
||||
:class:`~cryptography.x509.CertificateSigningRequestBuilder`.
|
||||
|
||||
0.9.1 - 2015-06-06
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -468,6 +468,76 @@ X.509 Revoked Certificate Object
|
|||
|
||||
The extensions encoded in the revoked certificate.
|
||||
|
||||
X.509 CSR (Certificate Signing Request) Builder Object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: CertificateSigningRequestBuilder
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from cryptography import x509
|
||||
>>> from cryptography.hazmat.backends import default_backend
|
||||
>>> from cryptography.hazmat.primitives import hashes
|
||||
>>> from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
>>> private_key = rsa.generate_private_key(
|
||||
... public_exponent=65537,
|
||||
... key_size=2048,
|
||||
... backend=default_backend()
|
||||
... )
|
||||
>>> builder = x509.CertificateSigningRequestBuilder()
|
||||
>>> builder = builder.subject_name(x509.Name([
|
||||
... x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
... ]))
|
||||
>>> builder = builder.add_extension(
|
||||
... x509.BasicConstraints(ca=False, path_length=None), critical=True,
|
||||
... )
|
||||
>>> request = builder.sign(
|
||||
... default_backend(), private_key, hashes.SHA256()
|
||||
... )
|
||||
>>> isinstance(request, x509.CertificateSigningRequest)
|
||||
True
|
||||
|
||||
.. method:: subject_name(name)
|
||||
|
||||
:param name: The :class:`~cryptography.x509.Name` of the certificate
|
||||
subject.
|
||||
:returns: A new
|
||||
:class:`~cryptography.x509.CertificateSigningRequestBuilder`.
|
||||
|
||||
.. method:: add_extension(extension, critical)
|
||||
|
||||
:param extension: The :class:`~cryptography.x509.Extension` to add to
|
||||
the request.
|
||||
:param critical: Set to `True` if the extension must be understood and
|
||||
handled by whoever reads the certificate.
|
||||
:returns: A new
|
||||
:class:`~cryptography.x509.CertificateSigningRequestBuilder`.
|
||||
|
||||
.. method:: sign(backend, private_key, algorithm)
|
||||
|
||||
:param backend: Backend that will be used to sign the request.
|
||||
Must support the
|
||||
:class:`~cryptography.hazmat.backends.interfaces.X509Backend`
|
||||
interface.
|
||||
|
||||
:param private_key: The
|
||||
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`,
|
||||
:class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or
|
||||
:class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`
|
||||
that will be used to sign the request. When the request is
|
||||
signed by a certificate authority, the private key's associated
|
||||
public key will be stored in the resulting certificate.
|
||||
|
||||
:param algorithm: The
|
||||
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
|
||||
that will be used to generate the request signature.
|
||||
|
||||
:returns: A new
|
||||
:class:`~cryptography.x509.CertificateSigningRequest`.
|
||||
|
||||
|
||||
.. class:: Name
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
|
|
|||
|
|
@ -274,6 +274,12 @@ class X509Backend(object):
|
|||
Load an X.509 CSR from PEM encoded data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_x509_csr(self, builder, private_key, algorithm):
|
||||
"""
|
||||
Create and sign an X.509 CSR from a CSR builder object.
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class DHBackend(object):
|
||||
|
|
|
|||
|
|
@ -342,3 +342,12 @@ class MultiBackend(object):
|
|||
"This backend does not support X.509.",
|
||||
_Reasons.UNSUPPORTED_X509
|
||||
)
|
||||
|
||||
def create_x509_csr(self, builder, private_key, algorithm):
|
||||
for b in self._filtered_backends(X509Backend):
|
||||
return b.create_x509_csr(builder, private_key, algorithm)
|
||||
|
||||
raise UnsupportedAlgorithm(
|
||||
"This backend does not support X.509.",
|
||||
_Reasons.UNSUPPORTED_X509
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from contextlib import contextmanager
|
|||
|
||||
import six
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography import utils, x509
|
||||
from cryptography.exceptions import (
|
||||
InternalError, UnsupportedAlgorithm, _Reasons
|
||||
)
|
||||
|
|
@ -56,6 +56,96 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError",
|
|||
["code", "lib", "func", "reason"])
|
||||
|
||||
|
||||
def _encode_asn1_int(backend, x):
|
||||
"""
|
||||
Converts a python integer to a ASN1_INTEGER. The returned ASN1_INTEGER will
|
||||
not be garbage collected (to support adding them to structs that take
|
||||
ownership of the object). Be sure to register it for GC if it will be
|
||||
discarded after use.
|
||||
|
||||
"""
|
||||
# Convert Python integer to OpenSSL "bignum" in case value exceeds
|
||||
# machine's native integer limits (note: `int_to_bn` doesn't automatically
|
||||
# GC).
|
||||
i = backend._int_to_bn(x)
|
||||
i = backend._ffi.gc(i, backend._lib.BN_free)
|
||||
|
||||
# Wrap in a ASN.1 integer. Don't GC -- as documented.
|
||||
i = backend._lib.BN_to_ASN1_INTEGER(i, backend._ffi.NULL)
|
||||
assert i != backend._ffi.NULL
|
||||
return i
|
||||
|
||||
|
||||
def _encode_asn1_str(backend, data, length):
|
||||
"""
|
||||
Create an ASN1_OCTET_STRING from a Python byte string.
|
||||
"""
|
||||
s = backend._lib.ASN1_OCTET_STRING_new()
|
||||
s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free)
|
||||
backend._lib.ASN1_OCTET_STRING_set(s, data, length)
|
||||
return s
|
||||
|
||||
|
||||
def _encode_name(backend, attributes):
|
||||
subject = backend._lib.X509_NAME_new()
|
||||
subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free)
|
||||
for attribute in attributes:
|
||||
value = attribute.value.encode('utf8')
|
||||
obj = _txt2obj(backend, attribute.oid.dotted_string)
|
||||
res = backend._lib.X509_NAME_add_entry_by_OBJ(
|
||||
subject,
|
||||
obj,
|
||||
backend._lib.MBSTRING_UTF8,
|
||||
value,
|
||||
-1, -1, 0,
|
||||
)
|
||||
assert res == 1
|
||||
return subject
|
||||
|
||||
|
||||
def _txt2obj(backend, name):
|
||||
"""
|
||||
Converts a Python string with an ASN.1 object ID in dotted form to a
|
||||
ASN1_OBJECT.
|
||||
"""
|
||||
name = name.encode('ascii')
|
||||
obj = backend._lib.OBJ_txt2obj(name, 1)
|
||||
assert obj != backend._ffi.NULL
|
||||
obj = backend._ffi.gc(obj, backend._lib.ASN1_OBJECT_free)
|
||||
return obj
|
||||
|
||||
|
||||
def _encode_basic_constraints(backend, basic_constraints, critical):
|
||||
obj = _txt2obj(backend, x509.OID_BASIC_CONSTRAINTS.dotted_string)
|
||||
assert obj is not None
|
||||
constraints = backend._lib.BASIC_CONSTRAINTS_new()
|
||||
constraints.ca = 255 if basic_constraints.ca else 0
|
||||
if basic_constraints.ca:
|
||||
constraints.pathlen = _encode_asn1_int(
|
||||
backend, basic_constraints.path_length
|
||||
)
|
||||
|
||||
# Fetch the encoded payload.
|
||||
pp = backend._ffi.new('unsigned char **')
|
||||
r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, pp)
|
||||
assert r > 0
|
||||
pp = backend._ffi.gc(
|
||||
pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
|
||||
)
|
||||
|
||||
# Wrap that in an X509 extension object.
|
||||
extension = backend._lib.X509_EXTENSION_create_by_OBJ(
|
||||
backend._ffi.NULL,
|
||||
obj,
|
||||
1 if critical else 0,
|
||||
_encode_asn1_str(backend, pp[0], r),
|
||||
)
|
||||
assert extension != backend._ffi.NULL
|
||||
|
||||
# Return the wrapped extension.
|
||||
return extension
|
||||
|
||||
|
||||
@utils.register_interface(CipherBackend)
|
||||
@utils.register_interface(CMACBackend)
|
||||
@utils.register_interface(DERSerializationBackend)
|
||||
|
|
@ -710,6 +800,79 @@ class Backend(object):
|
|||
def create_cmac_ctx(self, algorithm):
|
||||
return _CMACContext(self, algorithm)
|
||||
|
||||
def create_x509_csr(self, builder, private_key, algorithm):
|
||||
if not isinstance(algorithm, hashes.HashAlgorithm):
|
||||
raise TypeError('Algorithm must be a registered hash algorithm.')
|
||||
|
||||
if self._lib.OPENSSL_VERSION_NUMBER <= 0x10001000:
|
||||
if isinstance(private_key, _DSAPrivateKey):
|
||||
raise NotImplementedError(
|
||||
"Certificate signing requests aren't implemented for DSA"
|
||||
" keys on OpenSSL versions less than 1.0.1."
|
||||
)
|
||||
if isinstance(private_key, _EllipticCurvePrivateKey):
|
||||
raise NotImplementedError(
|
||||
"Certificate signing requests aren't implemented for EC"
|
||||
" keys on OpenSSL versions less than 1.0.1."
|
||||
)
|
||||
|
||||
# Resolve the signature algorithm.
|
||||
evp_md = self._lib.EVP_get_digestbyname(
|
||||
algorithm.name.encode('ascii')
|
||||
)
|
||||
assert evp_md != self._ffi.NULL
|
||||
|
||||
# Create an empty request.
|
||||
x509_req = self._lib.X509_REQ_new()
|
||||
assert x509_req != self._ffi.NULL
|
||||
x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free)
|
||||
|
||||
# Set x509 version.
|
||||
res = self._lib.X509_REQ_set_version(x509_req, x509.Version.v1.value)
|
||||
assert res == 1
|
||||
|
||||
# Set subject name.
|
||||
res = self._lib.X509_REQ_set_subject_name(
|
||||
x509_req, _encode_name(self, list(builder._subject_name))
|
||||
)
|
||||
assert res == 1
|
||||
|
||||
# Set subject public key.
|
||||
public_key = private_key.public_key()
|
||||
res = self._lib.X509_REQ_set_pubkey(
|
||||
x509_req, public_key._evp_pkey
|
||||
)
|
||||
assert res == 1
|
||||
|
||||
# Add extensions.
|
||||
extensions = self._lib.sk_X509_EXTENSION_new_null()
|
||||
assert extensions != self._ffi.NULL
|
||||
extensions = self._ffi.gc(
|
||||
extensions,
|
||||
self._lib.sk_X509_EXTENSION_free,
|
||||
)
|
||||
for extension in builder._extensions:
|
||||
if isinstance(extension.value, x509.BasicConstraints):
|
||||
extension = _encode_basic_constraints(
|
||||
self,
|
||||
extension.value,
|
||||
extension.critical
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError('Extension not yet supported.')
|
||||
res = self._lib.sk_X509_EXTENSION_push(extensions, extension)
|
||||
assert res == 1
|
||||
res = self._lib.X509_REQ_add_extensions(x509_req, extensions)
|
||||
assert res == 1
|
||||
|
||||
# Sign the request using the requester's private key.
|
||||
res = self._lib.X509_REQ_sign(
|
||||
x509_req, private_key._evp_pkey, evp_md
|
||||
)
|
||||
assert res > 0
|
||||
|
||||
return _CertificateSigningRequest(self, x509_req)
|
||||
|
||||
def load_pem_private_key(self, data, password):
|
||||
return self._load_key(
|
||||
self._lib.PEM_read_bio_PrivateKey,
|
||||
|
|
|
|||
|
|
@ -1442,3 +1442,44 @@ class RevokedCertificate(object):
|
|||
"""
|
||||
Returns an Extensions object containing a list of Revoked extensions.
|
||||
"""
|
||||
|
||||
|
||||
class CertificateSigningRequestBuilder(object):
|
||||
def __init__(self, subject_name=None, extensions=[]):
|
||||
"""
|
||||
Creates an empty X.509 certificate request (v1).
|
||||
"""
|
||||
self._subject_name = subject_name
|
||||
self._extensions = extensions
|
||||
|
||||
def subject_name(self, name):
|
||||
"""
|
||||
Sets the certificate requestor's distinguished name.
|
||||
"""
|
||||
if not isinstance(name, Name):
|
||||
raise TypeError('Expecting x509.Name object.')
|
||||
if self._subject_name is not None:
|
||||
raise ValueError('The subject name may only be set once.')
|
||||
return CertificateSigningRequestBuilder(name, self._extensions)
|
||||
|
||||
def add_extension(self, extension, critical):
|
||||
"""
|
||||
Adds an X.509 extension to the certificate request.
|
||||
"""
|
||||
if isinstance(extension, BasicConstraints):
|
||||
extension = Extension(OID_BASIC_CONSTRAINTS, critical, extension)
|
||||
else:
|
||||
raise NotImplementedError('Unsupported X.509 extension.')
|
||||
# TODO: This is quadratic in the number of extensions
|
||||
for e in self._extensions:
|
||||
if e.oid == extension.oid:
|
||||
raise ValueError('This extension has already been set.')
|
||||
return CertificateSigningRequestBuilder(
|
||||
self._subject_name, self._extensions + [extension]
|
||||
)
|
||||
|
||||
def sign(self, backend, private_key, algorithm):
|
||||
"""
|
||||
Signs the request using the requestor's private key.
|
||||
"""
|
||||
return backend.create_x509_csr(self, private_key, algorithm)
|
||||
|
|
|
|||
|
|
@ -203,6 +203,9 @@ class DummyX509Backend(object):
|
|||
def load_der_x509_csr(self, data):
|
||||
pass
|
||||
|
||||
def create_x509_csr(self, builder, private_key, algorithm):
|
||||
pass
|
||||
|
||||
|
||||
class TestMultiBackend(object):
|
||||
def test_ciphers(self):
|
||||
|
|
@ -480,6 +483,7 @@ class TestMultiBackend(object):
|
|||
backend.load_der_x509_certificate(b"certdata")
|
||||
backend.load_pem_x509_csr(b"reqdata")
|
||||
backend.load_der_x509_csr(b"reqdata")
|
||||
backend.create_x509_csr(object(), b"privatekey", hashes.SHA1())
|
||||
|
||||
backend = MultiBackend([])
|
||||
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
|
||||
|
|
@ -490,3 +494,5 @@ class TestMultiBackend(object):
|
|||
backend.load_pem_x509_csr(b"reqdata")
|
||||
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
|
||||
backend.load_der_x509_csr(b"reqdata")
|
||||
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
|
||||
backend.create_x509_csr(object(), b"privatekey", hashes.SHA1())
|
||||
|
|
|
|||
|
|
@ -21,14 +21,16 @@ from cryptography.hazmat.backends.openssl.backend import (
|
|||
)
|
||||
from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, padding
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding
|
||||
from cryptography.hazmat.primitives.ciphers import (
|
||||
BlockCipherAlgorithm, Cipher, CipherAlgorithm
|
||||
)
|
||||
from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
||||
from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR, Mode
|
||||
|
||||
from ..primitives.fixtures_dsa import DSA_KEY_2048
|
||||
from ..primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512
|
||||
from ..primitives.test_ec import _skip_curve_unsupported
|
||||
from ...utils import load_vectors_from_file, raises_unsupported_algorithm
|
||||
|
||||
|
||||
|
|
@ -453,6 +455,29 @@ class TestOpenSSLCMAC(object):
|
|||
backend.create_cmac_ctx(FakeAlgorithm())
|
||||
|
||||
|
||||
class TestOpenSSLCreateX509CSR(object):
|
||||
@pytest.mark.skipif(
|
||||
backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000,
|
||||
reason="Requires an older OpenSSL. Must be < 1.0.1"
|
||||
)
|
||||
def test_unsupported_dsa_keys(self):
|
||||
private_key = DSA_KEY_2048.private_key(backend)
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
backend.create_x509_csr(object(), private_key, hashes.SHA1())
|
||||
|
||||
@pytest.mark.skipif(
|
||||
backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000,
|
||||
reason="Requires an older OpenSSL. Must be < 1.0.1"
|
||||
)
|
||||
def test_unsupported_ec_keys(self):
|
||||
_skip_curve_unsupported(backend, ec.SECP256R1())
|
||||
private_key = ec.generate_private_key(ec.SECP256R1(), backend)
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
backend.create_x509_csr(object(), private_key, hashes.SHA1())
|
||||
|
||||
|
||||
class TestOpenSSLSerialisationWithOpenSSL(object):
|
||||
def test_pem_password_cb_buffer_too_small(self):
|
||||
ffi_cb, cb = backend._pem_password_cb(b"aa")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ from cryptography.hazmat.backends.interfaces import (
|
|||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
|
||||
|
||||
from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048
|
||||
from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048
|
||||
from .hazmat.primitives.test_ec import _skip_curve_unsupported
|
||||
from .utils import load_vectors_from_file
|
||||
|
||||
|
|
@ -586,7 +588,7 @@ class TestRSACertificateRequest(object):
|
|||
x509.Extension(
|
||||
x509.OID_BASIC_CONSTRAINTS,
|
||||
True,
|
||||
x509.BasicConstraints(True, 1),
|
||||
x509.BasicConstraints(ca=True, path_length=1),
|
||||
),
|
||||
]
|
||||
|
||||
|
|
@ -679,6 +681,222 @@ class TestRSACertificateRequest(object):
|
|||
assert serialized == request_bytes
|
||||
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=X509Backend)
|
||||
class TestCertificateSigningRequestBuilder(object):
|
||||
@pytest.mark.requires_backend_interface(interface=RSABackend)
|
||||
def test_sign_invalid_hash_algorithm(self, backend):
|
||||
private_key = RSA_KEY_2048.private_key(backend)
|
||||
|
||||
builder = x509.CertificateSigningRequestBuilder()
|
||||
with pytest.raises(TypeError):
|
||||
builder.sign(backend, private_key, 'NotAHash')
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=RSABackend)
|
||||
def test_build_ca_request_with_rsa(self, backend):
|
||||
private_key = RSA_KEY_2048.private_key(backend)
|
||||
|
||||
request = x509.CertificateSigningRequestBuilder().subject_name(
|
||||
x509.Name([
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
])
|
||||
).add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=2), critical=True
|
||||
).sign(
|
||||
backend, private_key, hashes.SHA1()
|
||||
)
|
||||
|
||||
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, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
]
|
||||
basic_constraints = request.extensions.get_extension_for_oid(
|
||||
x509.OID_BASIC_CONSTRAINTS
|
||||
)
|
||||
assert basic_constraints.value.ca is True
|
||||
assert basic_constraints.value.path_length == 2
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=RSABackend)
|
||||
def test_build_ca_request_with_unicode(self, backend):
|
||||
private_key = RSA_KEY_2048.private_key(backend)
|
||||
|
||||
request = x509.CertificateSigningRequestBuilder().subject_name(
|
||||
x509.Name([
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME,
|
||||
u'PyCA\U0001f37a'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
])
|
||||
).add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=2), critical=True
|
||||
).sign(
|
||||
backend, private_key, hashes.SHA1()
|
||||
)
|
||||
|
||||
loaded_request = x509.load_pem_x509_csr(
|
||||
request.public_bytes(encoding=serialization.Encoding.PEM), backend
|
||||
)
|
||||
subject = loaded_request.subject
|
||||
assert isinstance(subject, x509.Name)
|
||||
assert list(subject) == [
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA\U0001f37a'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
]
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=RSABackend)
|
||||
def test_build_nonca_request_with_rsa(self, backend):
|
||||
private_key = RSA_KEY_2048.private_key(backend)
|
||||
|
||||
request = x509.CertificateSigningRequestBuilder().subject_name(
|
||||
x509.Name([
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
])
|
||||
).add_extension(
|
||||
x509.BasicConstraints(ca=False, path_length=None), critical=True,
|
||||
).sign(
|
||||
backend, private_key, hashes.SHA1()
|
||||
)
|
||||
|
||||
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, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
]
|
||||
basic_constraints = request.extensions.get_extension_for_oid(
|
||||
x509.OID_BASIC_CONSTRAINTS
|
||||
)
|
||||
assert basic_constraints.value.ca is False
|
||||
assert basic_constraints.value.path_length is None
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
|
||||
def test_build_ca_request_with_ec(self, backend):
|
||||
if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000:
|
||||
pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1")
|
||||
|
||||
_skip_curve_unsupported(backend, ec.SECP256R1())
|
||||
private_key = ec.generate_private_key(ec.SECP256R1(), backend)
|
||||
|
||||
request = x509.CertificateSigningRequestBuilder().subject_name(
|
||||
x509.Name([
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
])
|
||||
).add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=2), critical=True
|
||||
).sign(
|
||||
backend, private_key, hashes.SHA1()
|
||||
)
|
||||
|
||||
assert isinstance(request.signature_hash_algorithm, hashes.SHA1)
|
||||
public_key = request.public_key()
|
||||
assert isinstance(public_key, ec.EllipticCurvePublicKey)
|
||||
subject = request.subject
|
||||
assert isinstance(subject, x509.Name)
|
||||
assert list(subject) == [
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
]
|
||||
basic_constraints = request.extensions.get_extension_for_oid(
|
||||
x509.OID_BASIC_CONSTRAINTS
|
||||
)
|
||||
assert basic_constraints.value.ca is True
|
||||
assert basic_constraints.value.path_length == 2
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=DSABackend)
|
||||
def test_build_ca_request_with_dsa(self, backend):
|
||||
if backend._lib.OPENSSL_VERSION_NUMBER < 0x10001000:
|
||||
pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1")
|
||||
|
||||
private_key = DSA_KEY_2048.private_key(backend)
|
||||
|
||||
request = x509.CertificateSigningRequestBuilder().subject_name(
|
||||
x509.Name([
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
])
|
||||
).add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=2), critical=True
|
||||
).sign(
|
||||
backend, private_key, hashes.SHA1()
|
||||
)
|
||||
|
||||
assert isinstance(request.signature_hash_algorithm, hashes.SHA1)
|
||||
public_key = request.public_key()
|
||||
assert isinstance(public_key, dsa.DSAPublicKey)
|
||||
subject = request.subject
|
||||
assert isinstance(subject, x509.Name)
|
||||
assert list(subject) == [
|
||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'),
|
||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, u'Texas'),
|
||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, u'Austin'),
|
||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, u'PyCA'),
|
||||
x509.NameAttribute(x509.OID_COMMON_NAME, u'cryptography.io'),
|
||||
]
|
||||
basic_constraints = request.extensions.get_extension_for_oid(
|
||||
x509.OID_BASIC_CONSTRAINTS
|
||||
)
|
||||
assert basic_constraints.value.ca is True
|
||||
assert basic_constraints.value.path_length == 2
|
||||
|
||||
def test_add_duplicate_extension(self, backend):
|
||||
builder = x509.CertificateSigningRequestBuilder().add_extension(
|
||||
x509.BasicConstraints(True, 2), critical=True,
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
builder.add_extension(
|
||||
x509.BasicConstraints(True, 2), critical=True,
|
||||
)
|
||||
|
||||
def test_set_invalid_subject(self, backend):
|
||||
builder = x509.CertificateSigningRequestBuilder()
|
||||
with pytest.raises(TypeError):
|
||||
builder.subject_name('NotAName')
|
||||
|
||||
def test_add_unsupported_extension(self, backend):
|
||||
builder = x509.CertificateSigningRequestBuilder()
|
||||
with pytest.raises(NotImplementedError):
|
||||
builder.add_extension(
|
||||
x509.AuthorityKeyIdentifier('keyid', None, None),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.requires_backend_interface(interface=DSABackend)
|
||||
@pytest.mark.requires_backend_interface(interface=X509Backend)
|
||||
class TestDSACertificate(object):
|
||||
|
|
|
|||
Loading…
Reference in a new issue