diff options
| -rw-r--r-- | docs/hazmat/backends/interfaces.rst | 8 | ||||
| -rw-r--r-- | docs/x509.rst | 95 | ||||
| -rw-r--r-- | src/cryptography/hazmat/backends/interfaces.py | 6 | ||||
| -rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 11 | ||||
| -rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 14 | ||||
| -rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 29 | ||||
| -rw-r--r-- | src/cryptography/hazmat/bindings/openssl/x509.py | 5 | ||||
| -rw-r--r-- | src/cryptography/x509.py | 26 | ||||
| -rw-r--r-- | tests/hazmat/backends/test_multibackend.py | 6 | ||||
| -rw-r--r-- | tests/test_x509.py | 67 | 
10 files changed, 265 insertions, 2 deletions
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 1af8d8f2..1f71f5d1 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -509,3 +509,11 @@ A specific ``backend`` may provide one or more of these interfaces.          :param bytes data: DER formatted certificate data.          :returns: An instance of :class:`~cryptography.x509.Certificate`. + +    .. method:: load_pem_x509_request(data) + +        .. versionadded:: 0.9 + +        :param bytes data: PEM formatted certificate request data. + +        :returns: An instance of :class:`~cryptography.x509.Request`. diff --git a/docs/x509.rst b/docs/x509.rst index f17c3dae..2ff12902 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -77,6 +77,58 @@ Loading Certificates      >>> cert.serial      2 +Loading Certificate Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. function:: load_pem_x509_request(data, backend) + +    .. versionadded:: 0.9 + +    Deserialize a certificate request from PEM encoded data. PEM requests are +    base64 decoded and have delimiters that look like +    ``-----BEGIN CERTIFICATE REQUEST-----``. This is also known as PKCS#10 +    format. + +    :param bytes data: The PEM encoded request data. + +    :param backend: A backend supporting the +        :class:`~cryptography.hazmat.backends.interfaces.X509Backend` +        interface. + +    :returns: An instance of :class:`~cryptography.x509.Request`. + +.. testsetup:: + +    pem_req_data = b""" +    -----BEGIN CERTIFICATE REQUEST----- +    MIIC0zCCAbsCAQAwWTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAw +    DgYDVQQHDAdDaGljYWdvMREwDwYDVQQKDAhyNTA5IExMQzESMBAGA1UEAwwJaGVs +    bG8uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqhZx+Mo9VRd9 +    vsnWWa6NBCws21rZ0+1B/JGgB4hDsZS7iDE4Bj5z4idheFRtl8bBbdjPknq7BfoF +    8v15Zq/Zv7i2xMSDL+LUrTBZezRd4bRTGqCm6YJ5EYkhqdcqeZleHCFImguHoq1J +    Fh0+kObQrTHXw3ZP57a3o1IvyIUA3nNoCBL0QQhwBXaDXOojMKNR+bqB5ve8GS1y +    Elr0AM/+cJsfaIahNQUgFKx3Eu3GeEOMKYOAG1lycgdQdmTUybLrT3U7vkClTseM +    xHg1r5En7ALjONIhqRuq3rddYahrP8HXozb3zUy3cJ7P6IeaosuvNzvMXOX9P6HD +    Ha9urDAJ1wIDAQABoDUwMwYJKoZIhvcNAQkOMSYwJDAiBgNVHREEGzAZggl3b3Js +    ZC5jb22CDHdoYXRldmVyLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAS4Ro6h+z52SK +    YSLCYARpnEu/rmh4jdqndt8naqcNb6uLx9mlKZ2W9on9XDjnSdQD9q+ZP5aZfESw +    R0+rJhW9ZrNa/g1pt6M24ihclHYDAxYMWxT1z/TXXGM3TmZZ6gfYlNE1kkBuODHa +    UYsR/1Ht1E1EsmmUimt2n+zQR2K8T9Coa+boaUW/GsTEuz1aaJAkj5ZvTDiIhRG4 +    AOCqFZOLAQmCCNgJnnspD9hDz/Ons085LF5wnYjN4/Nsk5tS6AGs3xjZ3jPoOGGn +    82WQ9m4dBGoVDZXsobVTaN592JEYwN5iu72zRn7Einb4V4H5y3yD2dD4yWPlt4pk +    5wFkeYsZEA== +    -----END CERTIFICATE REQUEST----- +    """.strip() + +.. doctest:: + +    >>> from cryptography import x509 +    >>> from cryptography.hazmat.backends import default_backend +    >>> from cryptography.hazmat.primitives import hashes +    >>> request = x509.load_pem_x509_request(pem_req_data, default_backend()) +    >>> isinstance(request.signature_hash_algorithm, hashes.SHA1) +    True +  X.509 Certificate Object  ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -211,6 +263,49 @@ X.509 Certificate Object              ...     print(ext)              <Extension(oid=<ObjectIdentifier(oid=2.5.29.19, name=basicConstraints)>, critical=True, value=<BasicConstraints(ca=True, path_length=None)>)> +X.509 Certificate Request Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: Request + +    .. versionadded:: 0.9 + +    .. method:: public_key() + +        :type: +            :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` or +            :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` or +            :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + +        The public key associated with the request. + +        .. doctest:: + +            >>> from cryptography.hazmat.primitives.asymmetric import rsa +            >>> public_key = request.public_key() +            >>> isinstance(public_key, rsa.RSAPublicKey) +            True + +    .. attribute:: subject + +        :type: :class:`Name` + +        The :class:`Name` of the subject. + +    .. attribute:: signature_hash_algorithm + +        :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + +        Returns the +        :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which +        was used in signing this request. + +        .. doctest:: + +            >>> from cryptography.hazmat.primitives import hashes +            >>> isinstance(request.signature_hash_algorithm, hashes.SHA1) +            True +  .. class:: Name      .. versionadded:: 0.8 diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index 79808909..44a3d81d 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -261,3 +261,9 @@ class X509Backend(object):          """          Load an X.509 certificate from DER encoded data.          """ + +    @abc.abstractmethod +    def load_pem_x509_request(self, data): +        """ +        Load an X.509 request from PEM encoded data. +        """ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 6e378c19..b09484ca 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -324,3 +324,14 @@ class MultiBackend(object):              "This backend does not support X.509.",              _Reasons.UNSUPPORTED_X509          ) + +    def load_pem_x509_request(self, data): +        for b in self._filtered_backends( +            X509Backend +        ): +            return b.load_pem_x509_request(data) + +        raise UnsupportedAlgorithm( +            "This backend does not support X.509.", +            _Reasons.UNSUPPORTED_X509 +        ) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 60aa4531..5dfc31c2 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -34,7 +34,7 @@ from cryptography.hazmat.backends.openssl.hmac import _HMACContext  from cryptography.hazmat.backends.openssl.rsa import (      _RSAPrivateKey, _RSAPublicKey  ) -from cryptography.hazmat.backends.openssl.x509 import _Certificate +from cryptography.hazmat.backends.openssl.x509 import _Certificate, _Request  from cryptography.hazmat.bindings.openssl.binding import Binding  from cryptography.hazmat.primitives import hashes, serialization  from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa @@ -820,6 +820,18 @@ class Backend(object):          x509 = self._ffi.gc(x509, self._lib.X509_free)          return _Certificate(self, x509) +    def load_pem_x509_request(self, data): +        mem_bio = self._bytes_to_bio(data) +        x509_req = self._lib.PEM_read_bio_X509_REQ( +            mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL +        ) +        if x509_req == self._ffi.NULL: +            self._consume_errors() +            raise ValueError("Unable to load request") + +        x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) +        return _Request(self, x509_req) +      def _load_key(self, openssl_read_func, convert_func, data, password):          mem_bio = self._bytes_to_bio(data) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 1c9cf5cf..fb767790 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -216,3 +216,32 @@ class _Certificate(object):              path_length = self._backend._bn_to_int(bn)          return x509.BasicConstraints(ca, path_length) + + +@utils.register_interface(x509.Request) +class _Request(object): +    def __init__(self, backend, x509_req): +        self._backend = backend +        self._x509_req = x509_req + +    def public_key(self): +        pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) +        assert pkey != self._backend._ffi.NULL +        pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) +        return self._backend._evp_pkey_to_public_key(pkey) + +    @property +    def subject(self): +        subject = self._backend._lib.X509_REQ_get_subject_name(self._x509_req) +        assert subject != self._backend._ffi.NULL +        return _build_x509_name(self._backend, subject) + +    @property +    def signature_hash_algorithm(self): +        oid = _obj2txt(self._backend, self._x509_req.sig_alg.algorithm) +        try: +            return x509._SIG_OIDS_TO_HASH[oid] +        except KeyError: +            raise UnsupportedAlgorithm( +                "Signature algorithm OID:{0} not recognized".format(oid) +            ) diff --git a/src/cryptography/hazmat/bindings/openssl/x509.py b/src/cryptography/hazmat/bindings/openssl/x509.py index 949a936e..e867450b 100644 --- a/src/cryptography/hazmat/bindings/openssl/x509.py +++ b/src/cryptography/hazmat/bindings/openssl/x509.py @@ -44,7 +44,10 @@ typedef struct {  typedef ... X509_EXTENSIONS; -typedef ... X509_REQ; +typedef struct { +    X509_ALGOR *sig_alg; +    ...; +} X509_REQ;  typedef struct {      ASN1_INTEGER *serialNumber; diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 864736e8..7b61e812 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -60,6 +60,10 @@ def load_der_x509_certificate(data, backend):      return backend.load_der_x509_certificate(data) +def load_pem_x509_request(data, backend): +    return backend.load_pem_x509_request(data) + +  class InvalidVersion(Exception):      def __init__(self, msg, parsed_version):          super(InvalidVersion, self).__init__(msg) @@ -336,3 +340,25 @@ class Certificate(object):          Returns a HashAlgorithm corresponding to the type of the digest signed          in the certificate.          """ + + +@six.add_metaclass(abc.ABCMeta) +class Request(object): +    @abc.abstractmethod +    def public_key(self): +        """ +        Returns the public key +        """ + +    @abc.abstractproperty +    def subject(self): +        """ +        Returns the subject name object. +        """ + +    @abc.abstractproperty +    def signature_hash_algorithm(self): +        """ +        Returns a HashAlgorithm corresponding to the type of the digest signed +        in the certificate. +        """ diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 5a8891c4..8f833ad2 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -197,6 +197,9 @@ class DummyX509Backend(object):      def load_der_x509_certificate(self, data):          pass +    def load_pem_x509_request(self, data): +        pass +  class TestMultiBackend(object):      def test_ciphers(self): @@ -472,9 +475,12 @@ class TestMultiBackend(object):          backend.load_pem_x509_certificate(b"certdata")          backend.load_der_x509_certificate(b"certdata") +        backend.load_pem_x509_request(b"reqdata")          backend = MultiBackend([])          with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):              backend.load_pem_x509_certificate(b"certdata")          with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):              backend.load_der_x509_certificate(b"certdata") +        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): +            backend.load_pem_x509_request(b"reqdata") diff --git a/tests/test_x509.py b/tests/test_x509.py index 2a472686..8f188885 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -340,6 +340,34 @@ class TestRSACertificate(object):          with pytest.raises(UnsupportedAlgorithm):              cert.signature_hash_algorithm +    def test_load_rsa_certificate_request(self, backend): +        request = _load_cert( +            os.path.join("x509", "requests", "rsa_sha1.pem"), +            x509.load_pem_x509_request, +            backend +        ) +        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_unsupported_signature_hash_algorithm_request(self, backend): +        request = _load_cert( +            os.path.join("x509", "requests", "rsa_md4.pem"), +            x509.load_pem_x509_request, +            backend +        ) +        with pytest.raises(UnsupportedAlgorithm): +            request.signature_hash_algorithm +  @pytest.mark.requires_backend_interface(interface=DSABackend)  @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -392,6 +420,25 @@ class TestDSACertificate(object):                  "822ff5d234e073b901cf5941f58e1f538e71d40d", 16              ) +    def test_load_dsa_request(self, backend): +        request = _load_cert( +            os.path.join("x509", "requests", "dsa_sha1.pem"), +            x509.load_pem_x509_request, +            backend +        ) +        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_COMMON_NAME, 'cryptography.io'), +            x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), +            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), +            x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), +            x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), +        ] +  @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)  @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -428,6 +475,26 @@ class TestECDSACertificate(object):          with pytest.raises(NotImplementedError):              cert.public_key() +    def test_load_ecdsa_certificate_request(self, backend): +        _skip_curve_unsupported(backend, ec.SECP384R1()) +        request = _load_cert( +            os.path.join("x509", "requests", "ec_sha256.pem"), +            x509.load_pem_x509_request, +            backend +        ) +        assert isinstance(request.signature_hash_algorithm, hashes.SHA256) +        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_COMMON_NAME, 'cryptography.io'), +            x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), +            x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), +            x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), +            x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), +        ] +  class TestNameAttribute(object):      def test_init_bad_oid(self):  | 
