aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.mention-bot2
-rw-r--r--CHANGELOG.rst20
-rw-r--r--docs/conf.py2
-rw-r--r--docs/development/submitting-patches.rst4
-rw-r--r--docs/development/test-vectors.rst4
-rw-r--r--docs/faq.rst2
-rw-r--r--docs/hazmat/backends/interfaces.rst35
-rw-r--r--docs/hazmat/bindings/openssl.rst2
-rw-r--r--docs/hazmat/primitives/asymmetric/ec.rst2
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst42
-rw-r--r--docs/installation.rst21
-rw-r--r--docs/limitations.rst2
-rw-r--r--docs/x509/reference.rst292
-rw-r--r--setup.py2
-rw-r--r--src/_cffi_src/build_commoncrypto.py1
-rw-r--r--src/_cffi_src/build_constant_time.py5
-rw-r--r--src/_cffi_src/build_openssl.py6
-rw-r--r--src/_cffi_src/build_padding.py5
-rw-r--r--src/_cffi_src/commoncrypto/sectrust.py22
-rw-r--r--src/_cffi_src/openssl/asn1.py3
-rw-r--r--src/_cffi_src/openssl/bignum.py2
-rw-r--r--src/_cffi_src/openssl/err.py1
-rw-r--r--src/_cffi_src/openssl/pem.py1
-rw-r--r--src/_cffi_src/openssl/x509.py13
-rw-r--r--src/_cffi_src/utils.py27
-rw-r--r--src/cryptography/__about__.py2
-rw-r--r--src/cryptography/hazmat/backends/interfaces.py14
-rw-r--r--src/cryptography/hazmat/backends/multibackend.py18
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py366
-rw-r--r--src/cryptography/hazmat/backends/openssl/rsa.py1
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py108
-rw-r--r--src/cryptography/hazmat/bindings/openssl/binding.py9
-rw-r--r--src/cryptography/utils.py3
-rw-r--r--src/cryptography/x509/__init__.py31
-rw-r--r--src/cryptography/x509/base.py156
-rw-r--r--src/cryptography/x509/extensions.py163
-rw-r--r--src/cryptography/x509/oid.py16
-rw-r--r--tests/hazmat/backends/test_multibackend.py14
-rw-r--r--tests/hazmat/backends/test_openssl.py105
-rw-r--r--tests/hazmat/primitives/test_rsa.py37
-rw-r--r--tests/test_x509.py116
-rw-r--r--tests/test_x509_crlbuilder.py449
-rw-r--r--tests/test_x509_ext.py328
-rw-r--r--tests/test_x509_revokedcertbuilder.py160
-rw-r--r--vectors/cryptography_vectors/__about__.py2
-rw-r--r--vectors/cryptography_vectors/x509/custom/unsupported_extension_2.pem14
46 files changed, 2355 insertions, 275 deletions
diff --git a/.mention-bot b/.mention-bot
index 3c998c98..a05018e6 100644
--- a/.mention-bot
+++ b/.mention-bot
@@ -1,3 +1,3 @@
{
- "userBlacklist": ["dreid"]
+ "userBlacklist": ["dreid", "exarkun", "dstufft"]
}
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3e24633e..717c9e71 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,21 @@ Changelog
.. note:: This version is not yet released and is under active development.
+* **BACKWARDS INCOMPATIBLE:**
+ :class:`~cryptography.x509.RevokedCertificate`
+ :attr:`~cryptography.x509.RevokedCertificate.extensions` now uses extension
+ classes rather than returning raw values inside the
+ :class:`~cryptography.x509.Extension`
+ :attr:`~cryptography.x509.Extension.value`. The new classes
+ are:
+
+ * :class:`~cryptography.x509.CertificateIssuer`
+ * :class:`~cryptography.x509.CRLReason`
+ * :class:`~cryptography.x509.InvalidityDate`
+* Deprecated support for OpenSSL 0.9.8 and 1.0.0. At this time there is no time
+ table for actually dropping support, however we strongly encourage all users
+ to upgrade, as those versions no longer receives support from the OpenSSL
+ project.
* The :class:`~cryptography.x509.Certificate` class now has
:attr:`~cryptography.x509.Certificate.signature` and
:attr:`~cryptography.x509.Certificate.tbs_certificate_bytes` attributes.
@@ -31,6 +46,11 @@ Changelog
* :class:`~cryptography.x509.AuthorityKeyIdentifier`
* :class:`~cryptography.x509.CRLNumber`
* :class:`~cryptography.x509.IssuerAlternativeName`
+* Added :class:`~cryptography.x509.CertificateRevocationListBuilder` and
+ :class:`~cryptography.x509.RevokedCertificateBuilder` to allow creation of
+ CRLs.
+* Unrecognized non-critical X.509 extensions are now parsed into an
+ :class:`~cryptography.x509.UnrecognizedExtension` object.
1.1.2 - 2015-12-10
~~~~~~~~~~~~~~~~~~
diff --git a/docs/conf.py b/docs/conf.py
index 5a4c41b1..dcc9c626 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -71,7 +71,7 @@ master_doc = 'index'
# General information about the project.
project = 'Cryptography'
-copyright = '2013-2015, Individual Contributors'
+copyright = '2013-2016, Individual Contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst
index 66105843..563bc81f 100644
--- a/docs/development/submitting-patches.rst
+++ b/docs/development/submitting-patches.rst
@@ -151,6 +151,6 @@ So, specifically:
.. _`Write comments as complete sentences.`: http://nedbatchelder.com/blog/201401/comments_should_be_sentences.html
.. _`syntax`: http://sphinx-doc.org/domains.html#info-field-lists
-.. _`Studies have shown`: https://smartbear.com/smartbear/media/pdfs/wp-cc-11-best-practices-of-peer-code-review.pdf
+.. _`Studies have shown`: https://smartbear.com/SmartBear/media/pdfs/11_Best_Practices_for_Peer_Code_Review.pdf
.. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev
-.. _`doc8`: https://github.com/stackforge/doc8
+.. _`doc8`: https://github.com/openstack/doc8
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index 70766d53..ad945f2f 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -141,6 +141,10 @@ Custom X.509 Vectors
* ``unsupported_extension.pem`` - An RSA 2048 bit self-signed certificate
containing an unsupported extension type. The OID was encoded as
"1.2.3.4" with an ``extnValue`` of "value".
+* ``unsupported_extension_2.pem`` - A ``secp256r1`` certificate
+ containing two unsupported extensions. The OIDs are ``1.3.6.1.4.1.41482.2``
+ with an ``extnValue`` of ``1.3.6.1.4.1.41482.1.2`` and
+ ``1.3.6.1.4.1.45724.2.1.1`` with an ``extnValue`` of ``\x03\x02\x040``
* ``unsupported_extension_critical.pem`` - An RSA 2048 bit self-signed
certificate containing an unsupported extension type marked critical. The OID
was encoded as "1.2.3.4" with an ``extnValue`` of "value".
diff --git a/docs/faq.rst b/docs/faq.rst
index 0b7bdce4..10c8656b 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -14,5 +14,5 @@ to NaCl.
If you prefer NaCl's design, we highly recommend `PyNaCl`_.
-.. _`NaCl`: http://nacl.cr.yp.to/
+.. _`NaCl`: https://nacl.cr.yp.to/
.. _`PyNaCl`: https://pynacl.readthedocs.org
diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst
index 442bd0de..73011dd0 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -547,8 +547,8 @@ A specific ``backend`` may provide one or more of these interfaces.
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
that will be used to generate the request signature.
- :returns: A new object with the
- :class:`~cryptography.x509.CertificateSigningRequest` interface.
+ :returns: A new instance of
+ :class:`~cryptography.x509.CertificateSigningRequest`.
.. method:: create_x509_certificate(builder, private_key, algorithm)
@@ -567,9 +567,36 @@ A specific ``backend`` may provide one or more of these interfaces.
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
that will be used to generate the certificate signature.
- :returns: A new object with the
- :class:`~cryptography.x509.Certificate` interface.
+ :returns: A new instance of :class:`~cryptography.x509.Certificate`.
+
+ .. method:: create_x509_crl(builder, private_key, algorithm)
+
+ .. versionadded:: 1.2
+
+ :param builder: An instance of
+ :class:`~cryptography.x509.CertificateRevocationListBuilder`.
+
+ :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 CRL.
+
+ :param algorithm: The
+ :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
+ that will be used to generate the CRL signature.
+
+ :returns: A new instance of
+ :class:`~cryptography.x509.CertificateRevocationList`.
+
+ .. method:: create_x509_revoked_certificate(builder)
+
+ .. versionadded:: 1.2
+
+ :param builder: An instance of RevokedCertificateBuilder.
+ :returns: A new instance of
+ :class:`~cryptography.x509.RevokedCertificate`.
.. class:: DHBackend
diff --git a/docs/hazmat/bindings/openssl.rst b/docs/hazmat/bindings/openssl.rst
index 0ec0a3d6..99cd7a48 100644
--- a/docs/hazmat/bindings/openssl.rst
+++ b/docs/hazmat/bindings/openssl.rst
@@ -46,4 +46,4 @@ OpenSSL.
.. _`CFFI`: https://cffi.readthedocs.org/
.. _`OpenSSL`: https://www.openssl.org/
-.. _`thread safety facilities`: https://www.openssl.org/docs/crypto/threads.html
+.. _`thread safety facilities`: https://www.openssl.org/docs/manmaster/crypto/threads.html
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index c1619dd0..8e3a3659 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -504,7 +504,7 @@ Key Interfaces
.. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters
.. _`less than 224 bits`: http://www.ecrypt.eu.org/ecrypt2/documents/D.SPA.20.pdf
.. _`elliptic curve diffie-hellman is faster than diffie-hellman`: http://digitalcommons.unl.edu/cgi/viewcontent.cgi?article=1100&context=cseconfwork
-.. _`minimize the number of security concerns for elliptic-curve cryptography`: http://cr.yp.to/ecdh/curve25519-20060209.pdf
+.. _`minimize the number of security concerns for elliptic-curve cryptography`: https://cr.yp.to/ecdh/curve25519-20060209.pdf
.. _`SafeCurves`: http://safecurves.cr.yp.to/
.. _`ECDSA`: https://en.wikipedia.org/wiki/ECDSA
.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index f14f4037..b94c0e10 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -118,7 +118,12 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END
:class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`
provider.
- :returns: A new instance of a private key.
+ :returns: One of
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`,
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`,
+ or
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`
+ depending on the contents of ``data``.
:raises ValueError: If the PEM data could not be decrypted or if its
structure could not be decoded successfully.
@@ -136,7 +141,8 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END
.. versionadded:: 0.6
Deserialize a public key from PEM encoded data to one of the supported
- asymmetric public key types.
+ asymmetric public key types. The PEM encoded data is typically a
+ ``subjectPublicKeyInfo`` payload as specified in :rfc:`5280`.
.. doctest::
@@ -151,7 +157,13 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END
:class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`
provider.
- :returns: A new instance of a public key.
+
+ :returns: One of
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`,
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`,
+ or
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
+ depending on the contents of ``data``.
:raises ValueError: If the PEM data's structure could not be decoded
successfully.
@@ -183,7 +195,12 @@ the rest.
:class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`
provider.
- :returns: A new instance of a private key.
+ :returns: One of
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`,
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`,
+ or
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`
+ depending on the contents of ``data``.
:raises ValueError: If the DER data could not be decrypted or if its
structure could not be decoded successfully.
@@ -210,7 +227,8 @@ the rest.
.. versionadded:: 0.8
Deserialize a public key from DER encoded data to one of the supported
- asymmetric public key types.
+ asymmetric public key types. The DER encoded data is typically a
+ ``subjectPublicKeyInfo`` payload as specified in :rfc:`5280`.
:param bytes data: The DER encoded key data.
@@ -218,7 +236,12 @@ the rest.
:class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`
provider.
- :returns: A new instance of a public key.
+ :returns: One of
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`,
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`,
+ or
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
+ depending on the contents of ``data``.
:raises ValueError: If the DER data's structure could not be decoded
successfully.
@@ -275,7 +298,12 @@ DSA keys look almost identical but begin with ``ssh-dss`` rather than
:class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`
depending on the key's type.
- :returns: A new instance of a public key type.
+ :returns: One of
+ :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`,
+ :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`,
+ or
+ :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
+ depending on the contents of ``data``.
:raises ValueError: If the OpenSSH data could not be properly decoded or
if the key is not in the proper format.
diff --git a/docs/installation.rst b/docs/installation.rst
index 16c42d2d..f9d2261a 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -21,6 +21,10 @@ Currently we test ``cryptography`` on Python 2.6, 2.7, 3.3, 3.4, 3.5, and PyPy
* x86-64 Debian Wheezy (7.x), Jessie (8.x), and Debian Sid (unstable)
* 32-bit and 64-bit Python on 64-bit Windows Server 2012
+.. warning::
+ Python 2.6 is no longer supported by the Python core team. A future version
+ of cryptography will drop support for this version.
+
We test compiling with ``clang`` as well as ``gcc`` and use the following
OpenSSL releases:
@@ -33,6 +37,11 @@ OpenSSL releases:
* ``OpenSSL 1.0.1f``
* ``OpenSSL 1.0.2-latest``
+.. warning::
+ OpenSSL versions 0.9.8 and 1.0.0 are no longer supported by the OpenSSL
+ project. A future version of cryptography will drop support for these
+ releases.
+
On Windows
----------
@@ -58,6 +67,8 @@ to include the proper locations. For example:
C:\> set INCLUDE=C:\OpenSSL-win64\include;%INCLUDE%
C:\> pip install cryptography
+If you need to rebuild ``cryptography`` for any reason be sure to clear the
+local `wheel cache`_.
.. _build-on-linux:
@@ -173,7 +184,7 @@ Building cryptography on OS X
-----------------------------
The wheel package on OS X is a statically linked build (as of 1.0.1) so for
-users on 10.10 (Yosemite) and above you only need one step:
+users with pip 1.5 or above you only need one step:
.. code-block:: console
@@ -182,8 +193,8 @@ users on 10.10 (Yosemite) and above you only need one step:
If you want to build cryptography yourself or are on an older OS X version
cryptography requires the presence of a C compiler, development headers, and
the proper libraries. On OS X much of this is provided by Apple's Xcode
-development tools. To install the Xcode command line tools open a terminal
-window and run:
+development tools. To install the Xcode command line tools (on OS X 10.9+)
+open a terminal window and run:
.. code-block:: console
@@ -227,6 +238,9 @@ You can also build cryptography statically:
$ sudo port install openssl
$ env CRYPTOGRAPHY_OSX_NO_LINK_FLAGS=1 LDFLAGS="/opt/local/lib/libssl.a /opt/local/lib/libcrypto.a" CFLAGS="-I/opt/local/include" pip install cryptography
+If you need to rebuild ``cryptography`` for any reason be sure to clear the
+local `wheel cache`_.
+
Building cryptography with conda
--------------------------------
@@ -257,3 +271,4 @@ information, consult `Greg Wilson's blog post`_ on the subject.
.. _`Greg Wilson's blog post`: http://software-carpentry.org/blog/2014/04/mr-biczo-was-right.html
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
.. _openssl.org: https://openssl.org/source/
+.. _`wheel cache`: https://pip.pypa.io/en/stable/reference/pip_install/#caching
diff --git a/docs/limitations.rst b/docs/limitations.rst
index 0dfc49ca..503bdfe4 100644
--- a/docs/limitations.rst
+++ b/docs/limitations.rst
@@ -15,5 +15,5 @@ software in Python is potentially vulnerable to this attack. The
Likelihood: unlikely, Remediation Cost: expensive to repair" and we do not
consider this a high risk for most users.
-.. _`Memory wiping`: http://blogs.msdn.com/b/oldnewthing/archive/2013/05/29/10421912.aspx
+.. _`Memory wiping`: https://blogs.msdn.microsoft.com/oldnewthing/20130529-00/?p=4223/
.. _`CERT secure coding guidelines`: https://www.securecoding.cert.org/confluence/display/c/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 4f4ce4fa..8bb3f40d 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -761,6 +761,113 @@ X.509 CSR (Certificate Signing Request) Object
key embedded in the CSR). This data may be used to validate the CSR
signature.
+X.509 Certificate Revocation List Builder
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: CertificateRevocationListBuilder
+
+ .. versionadded:: 1.2
+
+ .. 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
+ >>> from cryptography.x509.oid import NameOID
+ >>> import datetime
+ >>> one_day = datetime.timedelta(1, 0, 0)
+ >>> private_key = rsa.generate_private_key(
+ ... public_exponent=65537,
+ ... key_size=2048,
+ ... backend=default_backend()
+ ... )
+ >>> builder = x509.CertificateRevocationListBuilder()
+ >>> builder = builder.issuer_name(x509.Name([
+ ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io CA'),
+ ... ]))
+ >>> builder = builder.last_update(datetime.datetime.today())
+ >>> builder = builder.next_update(datetime.datetime.today() + one_day)
+ >>> revoked_cert = x509.RevokedCertificateBuilder().serial_number(
+ ... 333
+ ... ).revocation_date(
+ ... datetime.datetime.today()
+ ... ).build(default_backend())
+ >>> builder = builder.add_revoked_certificate(revoked_cert)
+ >>> crl = builder.sign(
+ ... private_key=private_key, algorithm=hashes.SHA256(),
+ ... backend=default_backend()
+ ... )
+ >>> len(crl)
+ 1
+
+ .. method:: issuer_name(name)
+
+ Sets the issuer's distinguished name.
+
+ :param name: The :class:`~cryptography.x509.Name` that describes the
+ issuer (CA).
+
+ .. method:: last_update(time)
+
+ Sets this CRL's activation time. This is the time from which
+ clients can start trusting this CRL. It may be different from
+ the time at which this CRL was created. This is also known as the
+ ``thisUpdate`` time.
+
+ :param time: The :class:`datetime.datetime` object (in UTC) that marks
+ the activation time for this CRL. The CRL may not be trusted if it
+ is used before this time.
+
+ .. method:: next_update(time)
+
+ Sets this CRL's next update time. This is the time by which
+ a new CRL will be issued. The CA is allowed to issue a new CRL before
+ this date, however clients are not required to check for it.
+
+ :param time: The :class:`datetime.datetime` object (in UTC) that marks
+ the next update time for this CRL.
+
+ .. method:: add_extension(extension, critical)
+
+ Adds an X.509 extension to this CRL.
+
+ :param extension: An extension with the
+ :class:`~cryptography.x509.ExtensionType` interface.
+
+ :param critical: Set to ``True`` if the extension must be understood and
+ handled by whoever reads the CRL.
+
+ .. method:: add_revoked_certificate(revoked_certificate)
+
+ Adds a revoked certificate to this CRL.
+
+ :param revoked_certificate: An instance of
+ :class:`~cryptography.x509.RevokedCertificate`. These can be
+ obtained from an existing CRL or created with
+ :class:`~cryptography.x509.RevokedCertificateBuilder`.
+
+ .. method:: sign(private_key, algorithm, backend)
+
+ Sign this CRL using the CA's private key.
+
+ :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 certificate.
+
+ :param algorithm: The
+ :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that
+ will be used to generate the signature.
+
+ :param backend: Backend that will be used to build the CRL.
+ Must support the
+ :class:`~cryptography.hazmat.backends.interfaces.X509Backend`
+ interface.
+
+ :returns: :class:`~cryptography.x509.CertificateRevocationList`
+
X.509 Revoked Certificate Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -800,8 +907,66 @@ X.509 Revoked Certificate Object
>>> for ext in revoked_certificate.extensions:
... print(ext)
- <Extension(oid=<ObjectIdentifier(oid=2.5.29.24, name=invalidityDate)>, critical=False, value=2015-01-01 00:00:00)>
- <Extension(oid=<ObjectIdentifier(oid=2.5.29.21, name=cRLReason)>, critical=False, value=ReasonFlags.key_compromise)>
+ <Extension(oid=<ObjectIdentifier(oid=2.5.29.24, name=invalidityDate)>, critical=False, value=<InvalidityDate(invalidity_date=2015-01-01 00:00:00)>)>
+ <Extension(oid=<ObjectIdentifier(oid=2.5.29.21, name=cRLReason)>, critical=False, value=<CRLReason(reason=ReasonFlags.key_compromise)>)>
+
+X.509 Revoked Certificate Builder
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: RevokedCertificateBuilder
+
+ This class is used to create :class:`~cryptography.x509.RevokedCertificate`
+ objects that can be used with the
+ :class:`~cryptography.x509.CertificateRevocationListBuilder`.
+
+ .. versionadded:: 1.2
+
+ .. doctest::
+
+ >>> from cryptography import x509
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> import datetime
+ >>> builder = x509.RevokedCertificateBuilder()
+ >>> builder = builder.revocation_date(datetime.datetime.today())
+ >>> builder = builder.serial_number(3333)
+ >>> revoked_certificate = builder.build(default_backend())
+ >>> isinstance(revoked_certificate, x509.RevokedCertificate)
+ True
+
+ .. method:: serial_number(serial_number)
+
+ Sets the revoked certificate's serial number.
+
+ :param serial_number: Integer number that is used to identify the
+ revoked certificate.
+
+ .. method:: revocation_date(time)
+
+ Sets the certificate's revocation date.
+
+ :param time: The :class:`datetime.datetime` object (in UTC) that marks the
+ revocation time for the certificate.
+
+ .. method:: add_extension(extension, critical)
+
+ Adds an X.509 extension to this revoked certificate.
+
+ :param extension: An instance of one of the
+ :ref:`CRL entry extensions <crl_entry_extensions>`.
+
+ :param critical: Set to ``True`` if the extension must be understood and
+ handled.
+
+ .. method:: build(backend)
+
+ Create a revoked certificate object using the provided backend.
+
+ :param backend: Backend that will be used to build the revoked
+ certificate. Must support the
+ :class:`~cryptography.hazmat.backends.interfaces.X509Backend`
+ interface.
+
+ :returns: :class:`~cryptography.x509.RevokedCertificate`
X.509 CSR (Certificate Signing Request) Builder Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1715,6 +1880,27 @@ X.509 Extensions
:type: int
+.. class:: UnrecognizedExtension
+
+ .. versionadded:: 1.2
+
+ A generic extension class used to hold the raw value of **non-critical**
+ extensions that ``cryptography`` does not know how to parse. Extensions
+ marked critical will raise
+ :class:`~cryptography.x509.UnsupportedExtension`.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns the OID associated with this extension.
+
+ .. attribute:: value
+
+ :type: byte
+
+ Returns the DER encoded bytes payload of the extension.
+
.. class:: CertificatePolicies(policies)
.. versionadded:: 0.9
@@ -1801,6 +1987,89 @@ These classes may be present within a :class:`CertificatePolicies` instance.
A list of integers.
+.. _crl_entry_extensions:
+
+CRL Entry Extensions
+~~~~~~~~~~~~~~~~~~~~
+
+These extensions are only valid within a :class:`RevokedCertificate` object.
+
+.. class:: CertificateIssuer(general_names)
+
+ .. versionadded:: 1.2
+
+ The certificate issuer is an extension that is only valid inside
+ :class:`~cryptography.x509.RevokedCertificate` objects. If the
+ ``indirectCRL`` property of the parent CRL's IssuingDistributionPoint
+ extension is set, then this extension identifies the certificate issuer
+ associated with the revoked certificate. The object is iterable to get
+ every element.
+
+ :param list general_names: A list of :class:`GeneralName` instances.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns
+ :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CERTIFICATE_ISSUER`.
+
+ .. method:: get_values_for_type(type)
+
+ :param type: A :class:`GeneralName` instance. This is one of the
+ :ref:`general name classes <general_name_classes>`.
+
+ :returns: A list of values extracted from the matched general names.
+ The type of the returned values depends on the :class:`GeneralName`.
+
+.. class:: CRLReason(reason)
+
+ .. versionadded:: 1.2
+
+ CRL reason (also known as ``reasonCode``) is an extension that is only
+ valid inside :class:`~cryptography.x509.RevokedCertificate` objects. It
+ identifies a reason for the certificate revocation.
+
+ :param reason: A value from the
+ :class:`~cryptography.x509.oid.CRLEntryExtensionOID` enum.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns
+ :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CRL_REASON`.
+
+ .. attribute:: reason
+
+ :type: An element from :class:`~cryptography.x509.ReasonFlags`
+
+.. class:: InvalidityDate(invalidity_date)
+
+ .. versionadded:: 1.2
+
+ Invalidity date is an extension that is only valid inside
+ :class:`~cryptography.x509.RevokedCertificate` objects. It provides
+ the date on which it is known or suspected that the private key was
+ compromised or that the certificate otherwise became invalid.
+ This date may be earlier than the revocation date in the CRL entry,
+ which is the date at which the CA processed the revocation.
+
+ :param invalidity_date: The :class:`datetime.datetime` when it is known
+ or suspected that the private key was compromised.
+
+ .. attribute:: oid
+
+ :type: :class:`ObjectIdentifier`
+
+ Returns
+ :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.INVALIDITY_DATE`.
+
+ .. attribute:: invalidity_date
+
+ :type: :class:`datetime.datetime`
+
+
Object Identifiers
~~~~~~~~~~~~~~~~~~
@@ -2123,6 +2392,22 @@ instances. The following common OIDs are available as constants.
the ``CRLNumber`` extension type. This extension only has meaning
for certificate revocation lists.
+.. class:: CRLEntryExtensionOID
+
+ .. versionadded:: 1.2
+
+ .. attribute:: CERTIFICATE_ISSUER
+
+ Corresponds to the dotted string ``"2.5.29.29"``.
+
+ .. attribute:: CRL_REASON
+
+ Corresponds to the dotted string ``"2.5.29.21"``.
+
+ .. attribute:: INVALIDITY_DATE
+
+ Corresponds to the dotted string ``"2.5.29.24"``.
+
Exceptions
~~~~~~~~~~
.. currentmodule:: cryptography.x509
@@ -2150,7 +2435,8 @@ Exceptions
.. class:: UnsupportedExtension
- This is raised when a certificate contains an unsupported extension type.
+ This is raised when a certificate contains an unsupported extension type
+ that is marked ``critical``.
.. attribute:: oid
diff --git a/setup.py b/setup.py
index 19f1e663..3b67e8ff 100644
--- a/setup.py
+++ b/setup.py
@@ -37,7 +37,7 @@ requirements = [
"idna>=2.0",
"pyasn1>=0.1.8",
"six>=1.4.1",
- "setuptools",
+ "setuptools>=1.0",
]
setup_requirements = []
diff --git a/src/_cffi_src/build_commoncrypto.py b/src/_cffi_src/build_commoncrypto.py
index 1c2692a7..4e69b6d1 100644
--- a/src/_cffi_src/build_commoncrypto.py
+++ b/src/_cffi_src/build_commoncrypto.py
@@ -22,6 +22,7 @@ ffi = build_ffi_for_binding(
"seckey",
"seckeychain",
"sectransform",
+ "sectrust",
],
extra_link_args=[
"-framework", "Security", "-framework", "CoreFoundation"
diff --git a/src/_cffi_src/build_constant_time.py b/src/_cffi_src/build_constant_time.py
index 6d9a8f54..7a11f7b5 100644
--- a/src/_cffi_src/build_constant_time.py
+++ b/src/_cffi_src/build_constant_time.py
@@ -5,9 +5,8 @@
from __future__ import absolute_import, division, print_function
import os
-import sys
-from _cffi_src.utils import build_ffi, extra_link_args
+from _cffi_src.utils import build_ffi, compiler_type, extra_link_args
with open(os.path.join(
@@ -24,5 +23,5 @@ ffi = build_ffi(
module_name="_constant_time",
cdef_source=types,
verify_source=functions,
- extra_link_args=extra_link_args(sys.platform),
+ extra_link_args=extra_link_args(compiler_type()),
)
diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py
index c856e3d9..c47b3082 100644
--- a/src/_cffi_src/build_openssl.py
+++ b/src/_cffi_src/build_openssl.py
@@ -7,7 +7,9 @@ from __future__ import absolute_import, division, print_function
import os
import sys
-from _cffi_src.utils import build_ffi_for_binding, extra_link_args
+from _cffi_src.utils import (
+ build_ffi_for_binding, compiler_type, extra_link_args
+)
def _get_openssl_libraries(platform):
@@ -92,5 +94,5 @@ ffi = build_ffi_for_binding(
pre_include=_OSX_PRE_INCLUDE,
post_include=_OSX_POST_INCLUDE,
libraries=_get_openssl_libraries(sys.platform),
- extra_link_args=extra_link_args(sys.platform),
+ extra_link_args=extra_link_args(compiler_type()),
)
diff --git a/src/_cffi_src/build_padding.py b/src/_cffi_src/build_padding.py
index 5df93d80..4c5096a1 100644
--- a/src/_cffi_src/build_padding.py
+++ b/src/_cffi_src/build_padding.py
@@ -5,9 +5,8 @@
from __future__ import absolute_import, division, print_function
import os
-import sys
-from _cffi_src.utils import build_ffi, extra_link_args
+from _cffi_src.utils import build_ffi, compiler_type, extra_link_args
with open(os.path.join(
@@ -24,5 +23,5 @@ ffi = build_ffi(
module_name="_padding",
cdef_source=types,
verify_source=functions,
- extra_link_args=extra_link_args(sys.platform),
+ extra_link_args=extra_link_args(compiler_type()),
)
diff --git a/src/_cffi_src/commoncrypto/sectrust.py b/src/_cffi_src/commoncrypto/sectrust.py
new file mode 100644
index 00000000..b787afad
--- /dev/null
+++ b/src/_cffi_src/commoncrypto/sectrust.py
@@ -0,0 +1,22 @@
+# 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
+
+INCLUDES = """
+#include <Security/SecTrust.h>
+"""
+
+TYPES = """
+"""
+
+FUNCTIONS = """
+OSStatus SecTrustCopyAnchorCertificates(CFArrayRef *);
+"""
+
+MACROS = """
+"""
+
+CUSTOMIZATIONS = """
+"""
diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py
index ddf4b9c5..30bd2451 100644
--- a/src/_cffi_src/openssl/asn1.py
+++ b/src/_cffi_src/openssl/asn1.py
@@ -95,13 +95,16 @@ ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *, time_t);
/* ASN1 GENERALIZEDTIME */
int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *, const char *);
+ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *, time_t);
void ASN1_GENERALIZEDTIME_free(ASN1_GENERALIZEDTIME *);
+int i2d_ASN1_GENERALIZEDTIME(ASN1_GENERALIZEDTIME *, unsigned char **);
/* ASN1 ENUMERATED */
ASN1_ENUMERATED *ASN1_ENUMERATED_new(void);
void ASN1_ENUMERATED_free(ASN1_ENUMERATED *);
int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long);
long ASN1_ENUMERATED_get(ASN1_ENUMERATED *);
+int i2d_ASN1_ENUMERATED(ASN1_ENUMERATED *, unsigned char **);
ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **, const unsigned char **, long,
const ASN1_ITEM *);
diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py
index ae035007..455afdc1 100644
--- a/src/_cffi_src/openssl/bignum.py
+++ b/src/_cffi_src/openssl/bignum.py
@@ -71,6 +71,8 @@ int BN_mask_bits(BIGNUM *, int);
"""
MACROS = """
+int BN_num_bytes(const BIGNUM *);
+
int BN_zero(BIGNUM *);
int BN_one(BIGNUM *);
int BN_mod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *);
diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py
index 6ec13775..9d97be16 100644
--- a/src/_cffi_src/openssl/err.py
+++ b/src/_cffi_src/openssl/err.py
@@ -230,6 +230,7 @@ static const int RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY;
static const int RSA_R_BLOCK_TYPE_IS_NOT_01;
static const int RSA_R_BLOCK_TYPE_IS_NOT_02;
static const int RSA_R_PKCS_DECODING_ERROR;
+static const int RSA_R_OAEP_DECODING_ERROR;
static const int RSA_F_RSA_SIGN;
"""
diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py
index 846e64e3..4eb6bb45 100644
--- a/src/_cffi_src/openssl/pem.py
+++ b/src/_cffi_src/openssl/pem.py
@@ -79,6 +79,7 @@ MACROS = """
int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *,
unsigned char *, int, pem_password_cb *,
void *);
+int PEM_write_bio_DHparams(BIO *, DH *);
"""
CUSTOMIZATIONS = """
diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py
index 0fc49ac5..c5eb600a 100644
--- a/src/_cffi_src/openssl/x509.py
+++ b/src/_cffi_src/openssl/x509.py
@@ -193,6 +193,8 @@ X509_EXTENSION *X509_REVOKED_get_ext(X509_REVOKED *, int);
int X509_REVOKED_add_ext(X509_REVOKED *, X509_EXTENSION*, int);
int X509_REVOKED_add1_ext_i2d(X509_REVOKED *, int, void *, int, unsigned long);
+int X509_REVOKED_set_revocationDate(X509_REVOKED *, ASN1_TIME *);
+
X509_CRL *X509_CRL_new(void);
X509_CRL *d2i_X509_CRL_bio(BIO *, X509_CRL **);
X509_EXTENSION *X509_CRL_get_ext(X509_CRL *, int);
@@ -268,6 +270,8 @@ void PKCS8_PRIV_KEY_INFO_free(PKCS8_PRIV_KEY_INFO *);
"""
MACROS = """
+X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *);
+
int i2d_X509_CINF(X509_CINF *, unsigned char **);
int i2d_X509_CRL_INFO(X509_CRL_INFO *, unsigned char **);
int i2d_X509_REQ_INFO(X509_REQ_INFO *, unsigned char **);
@@ -290,6 +294,7 @@ X509_EXTENSIONS *sk_X509_EXTENSION_new_null(void);
int sk_X509_EXTENSION_num(X509_EXTENSIONS *);
X509_EXTENSION *sk_X509_EXTENSION_value(X509_EXTENSIONS *, int);
int sk_X509_EXTENSION_push(X509_EXTENSIONS *, X509_EXTENSION *);
+int sk_X509_EXTENSION_insert(X509_EXTENSIONS *, X509_EXTENSION *, int);
X509_EXTENSION *sk_X509_EXTENSION_delete(X509_EXTENSIONS *, int);
void sk_X509_EXTENSION_free(X509_EXTENSIONS *);
@@ -362,4 +367,12 @@ int (*i2d_ECPrivateKey_bio)(BIO *, EC_KEY *) = NULL;
EC_KEY *(*o2i_ECPublicKey)(EC_KEY **, const unsigned char **, long) = NULL;
int (*i2o_ECPublicKey)(EC_KEY *, unsigned char **) = NULL;
#endif
+
+/* X509_REVOKED_dup only exists on 1.0.2+. It is implemented using
+ IMPLEMENT_ASN1_DUP_FUNCTION. The below is the equivalent so we have
+ it available on all OpenSSLs. */
+X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *rev) {
+ return ASN1_item_dup(ASN1_ITEM_rptr(X509_REVOKED), rev);
+}
+
"""
diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py
index 0b00353e..bdce2f3b 100644
--- a/src/_cffi_src/utils.py
+++ b/src/_cffi_src/utils.py
@@ -5,6 +5,8 @@
from __future__ import absolute_import, division, print_function
import sys
+from distutils.ccompiler import new_compiler
+from distutils.dist import Distribution
from cffi import FFI
@@ -79,10 +81,23 @@ def build_ffi(module_name, cdef_source, verify_source, libraries=[],
return ffi
-def extra_link_args(platform):
- if platform != "win32":
- return []
+def extra_link_args(compiler_type):
+ if compiler_type == 'msvc':
+ # Enable NX and ASLR for Windows builds on MSVC. These are enabled by
+ # default on Python 3.3+ but not on 2.x.
+ return ['/NXCOMPAT', '/DYNAMICBASE']
else:
- # Enable NX and ASLR for Windows builds. These are enabled by default
- # on Python 3.3+ but not on 2.x.
- return ["/NXCOMPAT", "/DYNAMICBASE"]
+ return []
+
+
+def compiler_type():
+ """
+ Gets the compiler type from distutils. On Windows with MSVC it will be
+ "msvc". On OS X and linux it is "unix".
+ """
+ dist = Distribution()
+ dist.parse_config_files()
+ cmd = dist.get_command_obj('build')
+ cmd.ensure_finalized()
+ compiler = new_compiler(compiler=cmd.compiler)
+ return compiler.compiler_type
diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py
index fbc1e735..f25cbc89 100644
--- a/src/cryptography/__about__.py
+++ b/src/cryptography/__about__.py
@@ -20,4 +20,4 @@ __author__ = "The cryptography developers"
__email__ = "cryptography-dev@python.org"
__license__ = "BSD or Apache License, Version 2.0"
-__copyright__ = "Copyright 2013-2015 {0}".format(__author__)
+__copyright__ = "Copyright 2013-2016 {0}".format(__author__)
diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py
index 92d9653a..5b9e6f38 100644
--- a/src/cryptography/hazmat/backends/interfaces.py
+++ b/src/cryptography/hazmat/backends/interfaces.py
@@ -292,6 +292,20 @@ class X509Backend(object):
Create and sign an X.509 certificate from a CertificateBuilder object.
"""
+ @abc.abstractmethod
+ def create_x509_crl(self, builder, private_key, algorithm):
+ """
+ Create and sign an X.509 CertificateRevocationList from a
+ CertificateRevocationListBuilder object.
+ """
+
+ @abc.abstractmethod
+ def create_x509_revoked_certificate(self, builder):
+ """
+ Create a RevokedCertificate object from a RevokedCertificateBuilder
+ object.
+ """
+
@six.add_metaclass(abc.ABCMeta)
class DHBackend(object):
diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py
index bbaaf424..65f18531 100644
--- a/src/cryptography/hazmat/backends/multibackend.py
+++ b/src/cryptography/hazmat/backends/multibackend.py
@@ -384,3 +384,21 @@ class MultiBackend(object):
"This backend does not support X.509.",
_Reasons.UNSUPPORTED_X509
)
+
+ def create_x509_crl(self, builder, private_key, algorithm):
+ for b in self._filtered_backends(X509Backend):
+ return b.create_x509_crl(builder, private_key, algorithm)
+
+ raise UnsupportedAlgorithm(
+ "This backend does not support X.509.",
+ _Reasons.UNSUPPORTED_X509
+ )
+
+ def create_x509_revoked_certificate(self, builder):
+ for b in self._filtered_backends(X509Backend):
+ return b.create_x509_revoked_certificate(builder)
+
+ 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 e69554f9..e8b0322e 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -37,8 +37,9 @@ from cryptography.hazmat.backends.openssl.rsa import (
_RSAPrivateKey, _RSAPublicKey
)
from cryptography.hazmat.backends.openssl.x509 import (
- _Certificate, _CertificateRevocationList, _CertificateSigningRequest,
- _DISTPOINT_TYPE_FULLNAME, _DISTPOINT_TYPE_RELATIVENAME
+ _CRL_ENTRY_REASON_ENUM_TO_CODE, _Certificate, _CertificateRevocationList,
+ _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME,
+ _DISTPOINT_TYPE_RELATIVENAME, _RevokedCertificate
)
from cryptography.hazmat.bindings._openssl import ffi as _ffi
from cryptography.hazmat.bindings.openssl import binding
@@ -53,7 +54,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import (
from cryptography.hazmat.primitives.ciphers.modes import (
CBC, CFB, CFB8, CTR, ECB, GCM, OFB
)
-from cryptography.x509.oid import ExtensionOID, NameOID
+from cryptography.x509.oid import CRLEntryExtensionOID, ExtensionOID, NameOID
_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"])
@@ -115,10 +116,9 @@ def _encode_asn1_str_gc(backend, data, length):
return s
-def _encode_inhibit_any_policy(backend, inhibit_any_policy):
- asn1int = _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs)
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_ASN1_INTEGER(asn1int, pp)
+def _encode_extension_to_der(backend, i2d_func, value):
+ pp = backend._ffi.new("unsigned char **")
+ r = i2d_func(value, pp)
backend.openssl_assert(r > 0)
pp = backend._ffi.gc(
pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
@@ -126,6 +126,13 @@ def _encode_inhibit_any_policy(backend, inhibit_any_policy):
return pp, r
+def _encode_inhibit_any_policy(backend, inhibit_any_policy):
+ asn1int = _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs)
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_ASN1_INTEGER, asn1int
+ )
+
+
def _encode_name(backend, attributes):
"""
The X509_NAME created will not be gc'd. Use _encode_name_gc if needed.
@@ -153,6 +160,41 @@ def _encode_name_gc(backend, attributes):
return subject
+def _encode_crl_number(backend, crl_number):
+ asn1int = _encode_asn1_int_gc(backend, crl_number.crl_number)
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_ASN1_INTEGER, asn1int
+ )
+
+
+def _encode_crl_reason(backend, crl_reason):
+ asn1enum = backend._lib.ASN1_ENUMERATED_new()
+ backend.openssl_assert(asn1enum != backend._ffi.NULL)
+ asn1enum = backend._ffi.gc(asn1enum, backend._lib.ASN1_ENUMERATED_free)
+ res = backend._lib.ASN1_ENUMERATED_set(
+ asn1enum, _CRL_ENTRY_REASON_ENUM_TO_CODE[crl_reason.reason]
+ )
+ backend.openssl_assert(res == 1)
+
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_ASN1_ENUMERATED, asn1enum
+ )
+
+
+def _encode_invalidity_date(backend, invalidity_date):
+ time = backend._lib.ASN1_GENERALIZEDTIME_set(
+ backend._ffi.NULL, calendar.timegm(
+ invalidity_date.invalidity_date.timetuple()
+ )
+ )
+ backend.openssl_assert(time != backend._ffi.NULL)
+ time = backend._ffi.gc(time, backend._lib.ASN1_GENERALIZEDTIME_free)
+
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_ASN1_GENERALIZEDTIME, time
+ )
+
+
def _encode_certificate_policies(backend, certificate_policies):
cp = backend._lib.sk_POLICYINFO_new_null()
backend.openssl_assert(cp != backend._ffi.NULL)
@@ -200,13 +242,9 @@ def _encode_certificate_policies(backend, certificate_policies):
pi.qualifiers = pqis
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_CERTIFICATEPOLICIES(cp, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_CERTIFICATEPOLICIES, cp
)
- return pp, r
def _encode_notice_reference(backend, notice):
@@ -282,13 +320,9 @@ def _encode_key_usage(backend, key_usage):
res = set_bit(ku, 8, 0)
backend.openssl_assert(res == 1)
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_ASN1_BIT_STRING(ku, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_ASN1_BIT_STRING, ku
)
- return pp, r
def _encode_authority_key_identifier(backend, authority_keyid):
@@ -312,13 +346,9 @@ def _encode_authority_key_identifier(backend, authority_keyid):
backend, authority_keyid.authority_cert_serial_number
)
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_AUTHORITY_KEYID(akid, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_AUTHORITY_KEYID, akid
)
- return pp, r
def _encode_basic_constraints(backend, basic_constraints):
@@ -332,14 +362,9 @@ def _encode_basic_constraints(backend, basic_constraints):
backend, basic_constraints.path_length
)
- # Fetch the encoded payload.
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_BASIC_CONSTRAINTS(constraints, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_BASIC_CONSTRAINTS, constraints
)
- return pp, r
def _encode_authority_information_access(backend, authority_info_access):
@@ -359,13 +384,9 @@ def _encode_authority_information_access(backend, authority_info_access):
res = backend._lib.sk_ACCESS_DESCRIPTION_push(aia, ad)
backend.openssl_assert(res >= 1)
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_AUTHORITY_INFO_ACCESS(aia, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_AUTHORITY_INFO_ACCESS, aia
)
- return pp, r
def _encode_general_names(backend, names):
@@ -384,24 +405,16 @@ def _encode_alt_name(backend, san):
general_names = backend._ffi.gc(
general_names, backend._lib.GENERAL_NAMES_free
)
- pp = backend._ffi.new("unsigned char **")
- r = backend._lib.i2d_GENERAL_NAMES(general_names, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_GENERAL_NAMES, general_names
)
- return pp, r
def _encode_subject_key_identifier(backend, ski):
asn1_str = _encode_asn1_str_gc(backend, ski.digest, len(ski.digest))
- pp = backend._ffi.new("unsigned char **")
- r = backend._lib.i2d_ASN1_OCTET_STRING(asn1_str, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_ASN1_OCTET_STRING, asn1_str
)
- return pp, r
def _encode_general_name(backend, name):
@@ -499,15 +512,10 @@ def _encode_extended_key_usage(backend, extended_key_usage):
res = backend._lib.sk_ASN1_OBJECT_push(eku, obj)
backend.openssl_assert(res >= 1)
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_EXTENDED_KEY_USAGE(
- backend._ffi.cast("EXTENDED_KEY_USAGE *", eku), pp
- )
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ eku_ptr = backend._ffi.cast("EXTENDED_KEY_USAGE *", eku)
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_EXTENDED_KEY_USAGE, eku_ptr
)
- return pp, r
_CRLREASONFLAGS = {
@@ -562,13 +570,9 @@ def _encode_crl_distribution_points(backend, crl_distribution_points):
res = backend._lib.sk_DIST_POINT_push(cdp, dp)
backend.openssl_assert(res >= 1)
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.i2d_CRL_DIST_POINTS(cdp, pp)
- backend.openssl_assert(r > 0)
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.i2d_CRL_DIST_POINTS, cdp
)
- return pp, r
def _encode_name_constraints(backend, name_constraints):
@@ -584,13 +588,9 @@ def _encode_name_constraints(backend, name_constraints):
)
nc.excludedSubtrees = excluded
- pp = backend._ffi.new('unsigned char **')
- r = backend._lib.Cryptography_i2d_NAME_CONSTRAINTS(nc, pp)
- assert r > 0
- pp = backend._ffi.gc(
- pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0])
+ return _encode_extension_to_der(
+ backend, backend._lib.Cryptography_i2d_NAME_CONSTRAINTS, nc
)
- return pp, r
def _encode_general_subtree(backend, subtrees):
@@ -625,6 +625,21 @@ _EXTENSION_ENCODE_HANDLERS = {
ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints,
}
+_CRL_EXTENSION_ENCODE_HANDLERS = {
+ ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name,
+ ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier,
+ ExtensionOID.AUTHORITY_INFORMATION_ACCESS: (
+ _encode_authority_information_access
+ ),
+ ExtensionOID.CRL_NUMBER: _encode_crl_number,
+}
+
+_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS = {
+ CRLEntryExtensionOID.CERTIFICATE_ISSUER: _encode_alt_name,
+ CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason,
+ CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date,
+}
+
class _PasswordUserdata(object):
def __init__(self, password):
@@ -899,17 +914,14 @@ class Backend(object):
assert bn != self._ffi.NULL
if six.PY3:
# Python 3 has constant time from_bytes, so use that.
-
- bn_num_bytes = (self._lib.BN_num_bits(bn) + 7) // 8
+ bn_num_bytes = self._lib.BN_num_bytes(bn)
bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes)
bin_len = self._lib.BN_bn2bin(bn, bin_ptr)
# A zero length means the BN has value 0
self.openssl_assert(bin_len >= 0)
return int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big")
-
else:
# Under Python 2 the best we can do is hex()
-
hex_cdata = self._lib.BN_bn2hex(bn)
self.openssl_assert(hex_cdata != self._ffi.NULL)
hex_str = self._ffi.string(hex_cdata)
@@ -1312,30 +1324,21 @@ class Backend(object):
self.openssl_assert(res == 1)
# Add extensions.
- extensions = self._lib.sk_X509_EXTENSION_new_null()
- self.openssl_assert(extensions != self._ffi.NULL)
- extensions = self._ffi.gc(
- extensions,
- self._lib.sk_X509_EXTENSION_free,
+ sk_extension = self._lib.sk_X509_EXTENSION_new_null()
+ self.openssl_assert(sk_extension != self._ffi.NULL)
+ sk_extension = self._ffi.gc(
+ sk_extension, self._lib.sk_X509_EXTENSION_free
)
- for extension in builder._extensions:
- try:
- encode = _EXTENSION_ENCODE_HANDLERS[extension.oid]
- except KeyError:
- raise NotImplementedError('Extension not yet supported.')
-
- pp, r = encode(self, extension.value)
- obj = _txt2obj_gc(self, extension.oid.dotted_string)
- extension = self._lib.X509_EXTENSION_create_by_OBJ(
- self._ffi.NULL,
- obj,
- 1 if extension.critical else 0,
- _encode_asn1_str_gc(self, pp[0], r),
- )
- self.openssl_assert(extension != self._ffi.NULL)
- res = self._lib.sk_X509_EXTENSION_push(extensions, extension)
- self.openssl_assert(res >= 1)
- res = self._lib.X509_REQ_add_extensions(x509_req, extensions)
+ # gc is not necessary for CSRs, as sk_X509_EXTENSION_free
+ # will release all the X509_EXTENSIONs.
+ self._create_x509_extensions(
+ extensions=builder._extensions,
+ handlers=_EXTENSION_ENCODE_HANDLERS,
+ x509_obj=sk_extension,
+ add_func=self._lib.sk_X509_EXTENSION_insert,
+ gc=False
+ )
+ res = self._lib.X509_REQ_add_extensions(x509_req, sk_extension)
self.openssl_assert(res == 1)
# Sign the request using the requester's private key.
@@ -1416,24 +1419,13 @@ class Backend(object):
self.openssl_assert(res != self._ffi.NULL)
# Add extensions.
- for i, extension in enumerate(builder._extensions):
- try:
- encode = _EXTENSION_ENCODE_HANDLERS[extension.oid]
- except KeyError:
- raise NotImplementedError('Extension not yet supported.')
-
- pp, r = encode(self, extension.value)
- obj = _txt2obj_gc(self, extension.oid.dotted_string)
- extension = self._lib.X509_EXTENSION_create_by_OBJ(
- self._ffi.NULL,
- obj,
- 1 if extension.critical else 0,
- _encode_asn1_str_gc(self, pp[0], r)
- )
- self.openssl_assert(extension != self._ffi.NULL)
- extension = self._ffi.gc(extension, self._lib.X509_EXTENSION_free)
- res = self._lib.X509_add_ext(x509_cert, extension, i)
- self.openssl_assert(res == 1)
+ self._create_x509_extensions(
+ extensions=builder._extensions,
+ handlers=_EXTENSION_ENCODE_HANDLERS,
+ x509_obj=x509_cert,
+ add_func=self._lib.X509_add_ext,
+ gc=True
+ )
# Set the issuer name.
res = self._lib.X509_set_issuer_name(
@@ -1455,6 +1447,147 @@ class Backend(object):
return _Certificate(self, x509_cert)
+ def create_x509_crl(self, builder, private_key, algorithm):
+ if not isinstance(builder, x509.CertificateRevocationListBuilder):
+ raise TypeError('Builder type mismatch.')
+ 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(
+ "CRL signatures aren't implemented for DSA"
+ " keys on OpenSSL versions less than 1.0.1."
+ )
+ if isinstance(private_key, _EllipticCurvePrivateKey):
+ raise NotImplementedError(
+ "CRL signatures aren't implemented for EC"
+ " keys on OpenSSL versions less than 1.0.1."
+ )
+
+ evp_md = self._lib.EVP_get_digestbyname(
+ algorithm.name.encode('ascii')
+ )
+ self.openssl_assert(evp_md != self._ffi.NULL)
+
+ # Create an empty CRL.
+ x509_crl = self._lib.X509_CRL_new()
+ x509_crl = self._ffi.gc(x509_crl, backend._lib.X509_CRL_free)
+
+ # Set the x509 CRL version. We only support v2 (integer value 1).
+ res = self._lib.X509_CRL_set_version(x509_crl, 1)
+ self.openssl_assert(res == 1)
+
+ # Set the issuer name.
+ res = self._lib.X509_CRL_set_issuer_name(
+ x509_crl, _encode_name_gc(self, list(builder._issuer_name))
+ )
+ self.openssl_assert(res == 1)
+
+ # Set the last update time.
+ last_update = self._lib.ASN1_TIME_set(
+ self._ffi.NULL, calendar.timegm(builder._last_update.timetuple())
+ )
+ self.openssl_assert(last_update != self._ffi.NULL)
+ last_update = self._ffi.gc(last_update, self._lib.ASN1_TIME_free)
+ res = self._lib.X509_CRL_set_lastUpdate(x509_crl, last_update)
+ self.openssl_assert(res == 1)
+
+ # Set the next update time.
+ next_update = self._lib.ASN1_TIME_set(
+ self._ffi.NULL, calendar.timegm(builder._next_update.timetuple())
+ )
+ self.openssl_assert(next_update != self._ffi.NULL)
+ next_update = self._ffi.gc(next_update, self._lib.ASN1_TIME_free)
+ res = self._lib.X509_CRL_set_nextUpdate(x509_crl, next_update)
+ self.openssl_assert(res == 1)
+
+ # Add extensions.
+ self._create_x509_extensions(
+ extensions=builder._extensions,
+ handlers=_CRL_EXTENSION_ENCODE_HANDLERS,
+ x509_obj=x509_crl,
+ add_func=self._lib.X509_CRL_add_ext,
+ gc=True
+ )
+
+ # add revoked certificates
+ for revoked_cert in builder._revoked_certificates:
+ # Duplicating because the X509_CRL takes ownership and will free
+ # this memory when X509_CRL_free is called.
+ revoked = self._lib.Cryptography_X509_REVOKED_dup(
+ revoked_cert._x509_revoked
+ )
+ self.openssl_assert(revoked != self._ffi.NULL)
+ res = self._lib.X509_CRL_add0_revoked(x509_crl, revoked)
+ self.openssl_assert(res == 1)
+
+ res = self._lib.X509_CRL_sign(
+ x509_crl, private_key._evp_pkey, evp_md
+ )
+ if res == 0:
+ errors = self._consume_errors()
+ self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA)
+ self.openssl_assert(
+ errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+ )
+ raise ValueError("Digest too big for RSA key")
+
+ return _CertificateRevocationList(self, x509_crl)
+
+ def _create_x509_extensions(self, extensions, handlers, x509_obj,
+ add_func, gc):
+ for i, extension in enumerate(extensions):
+ try:
+ encode = handlers[extension.oid]
+ except KeyError:
+ raise NotImplementedError(
+ 'Extension not supported: {0}'.format(extension.oid)
+ )
+
+ pp, r = encode(self, extension.value)
+ obj = _txt2obj_gc(self, extension.oid.dotted_string)
+ x509_extension = self._lib.X509_EXTENSION_create_by_OBJ(
+ self._ffi.NULL,
+ obj,
+ 1 if extension.critical else 0,
+ _encode_asn1_str_gc(self, pp[0], r)
+ )
+ self.openssl_assert(x509_extension != self._ffi.NULL)
+ if gc:
+ x509_extension = self._ffi.gc(
+ x509_extension, self._lib.X509_EXTENSION_free
+ )
+ res = add_func(x509_obj, x509_extension, i)
+ self.openssl_assert(res >= 1)
+
+ def create_x509_revoked_certificate(self, builder):
+ if not isinstance(builder, x509.RevokedCertificateBuilder):
+ raise TypeError('Builder type mismatch.')
+
+ x509_revoked = self._lib.X509_REVOKED_new()
+ self.openssl_assert(x509_revoked != self._ffi.NULL)
+ x509_revoked = self._ffi.gc(x509_revoked, self._lib.X509_REVOKED_free)
+ serial_number = _encode_asn1_int_gc(self, builder._serial_number)
+ res = self._lib.X509_REVOKED_set_serialNumber(
+ x509_revoked, serial_number
+ )
+ self.openssl_assert(res == 1)
+ res = self._lib.ASN1_TIME_set(
+ x509_revoked.revocationDate,
+ calendar.timegm(builder._revocation_date.timetuple())
+ )
+ self.openssl_assert(res != self._ffi.NULL)
+ # add CRL entry extensions
+ self._create_x509_extensions(
+ extensions=builder._extensions,
+ handlers=_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS,
+ x509_obj=x509_revoked,
+ add_func=self._lib.X509_REVOKED_add_ext,
+ gc=True
+ )
+ return _RevokedCertificate(self, None, x509_revoked)
+
def load_pem_private_key(self, data, password):
return self._load_key(
self._lib.PEM_read_bio_PrivateKey,
@@ -2136,6 +2269,9 @@ class Backend(object):
generalized_time = self._ffi.gc(
generalized_time, self._lib.ASN1_GENERALIZEDTIME_free
)
+ return self._parse_asn1_generalized_time(generalized_time)
+
+ def _parse_asn1_generalized_time(self, generalized_time):
time = self._asn1_string_to_ascii(
self._ffi.cast("ASN1_STRING *", generalized_time)
)
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
index 664f6d35..033cd3b1 100644
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ b/src/cryptography/hazmat/backends/openssl/rsa.py
@@ -138,6 +138,7 @@ def _handle_rsa_enc_dec_error(backend, key):
decoding_errors = [
backend._lib.RSA_R_BLOCK_TYPE_IS_NOT_01,
backend._lib.RSA_R_BLOCK_TYPE_IS_NOT_02,
+ backend._lib.RSA_R_OAEP_DECODING_ERROR,
]
if backend._lib.Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR:
decoding_errors.append(backend._lib.RSA_R_PKCS_DECODING_ERROR)
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 7e89ac67..b8614e0b 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -4,8 +4,8 @@
from __future__ import absolute_import, division, print_function
-import datetime
import ipaddress
+import operator
from email.utils import parseaddr
@@ -19,7 +19,7 @@ from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.x509.oid import (
- CRLExtensionOID, CertificatePoliciesOID, ExtensionOID
+ CRLEntryExtensionOID, CertificatePoliciesOID, ExtensionOID
)
@@ -213,6 +213,15 @@ class _X509ExtensionParser(object):
"Critical extension {0} is not currently supported"
.format(oid), oid
)
+ else:
+ # Dump the DER payload into an UnrecognizedExtension object
+ data = backend._lib.X509_EXTENSION_get_data(ext)
+ backend.openssl_assert(data != backend._ffi.NULL)
+ der = backend._ffi.buffer(data.data, data.length)[:]
+ unrecognized = x509.UnrecognizedExtension(oid, der)
+ extensions.append(
+ x509.Extension(oid, critical, unrecognized)
+ )
else:
# For extensions which are not supported by OpenSSL we pass the
# extension object directly to the parsing routine so it can
@@ -679,7 +688,19 @@ def _decode_inhibit_any_policy(backend, asn1_int):
return x509.InhibitAnyPolicy(skip_certs)
-_CRL_REASON_CODE_TO_ENUM = {
+# CRLReason ::= ENUMERATED {
+# unspecified (0),
+# keyCompromise (1),
+# cACompromise (2),
+# affiliationChanged (3),
+# superseded (4),
+# cessationOfOperation (5),
+# certificateHold (6),
+# -- value 7 is not used
+# removeFromCRL (8),
+# privilegeWithdrawn (9),
+# aACompromise (10) }
+_CRL_ENTRY_REASON_CODE_TO_ENUM = {
0: x509.ReasonFlags.unspecified,
1: x509.ReasonFlags.key_compromise,
2: x509.ReasonFlags.ca_compromise,
@@ -693,13 +714,27 @@ _CRL_REASON_CODE_TO_ENUM = {
}
+_CRL_ENTRY_REASON_ENUM_TO_CODE = {
+ x509.ReasonFlags.unspecified: 0,
+ x509.ReasonFlags.key_compromise: 1,
+ x509.ReasonFlags.ca_compromise: 2,
+ x509.ReasonFlags.affiliation_changed: 3,
+ x509.ReasonFlags.superseded: 4,
+ x509.ReasonFlags.cessation_of_operation: 5,
+ x509.ReasonFlags.certificate_hold: 6,
+ x509.ReasonFlags.remove_from_crl: 8,
+ x509.ReasonFlags.privilege_withdrawn: 9,
+ x509.ReasonFlags.aa_compromise: 10
+}
+
+
def _decode_crl_reason(backend, enum):
enum = backend._ffi.cast("ASN1_ENUMERATED *", enum)
enum = backend._ffi.gc(enum, backend._lib.ASN1_ENUMERATED_free)
code = backend._lib.ASN1_ENUMERATED_get(enum)
try:
- return _CRL_REASON_CODE_TO_ENUM[code]
+ return x509.CRLReason(_CRL_ENTRY_REASON_CODE_TO_ENUM[code])
except KeyError:
raise ValueError("Unsupported reason code: {0}".format(code))
@@ -711,12 +746,9 @@ def _decode_invalidity_date(backend, inv_date):
generalized_time = backend._ffi.gc(
generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free
)
- time = backend._ffi.string(
- backend._lib.ASN1_STRING_data(
- backend._ffi.cast("ASN1_STRING *", generalized_time)
- )
- ).decode("ascii")
- return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ")
+ return x509.InvalidityDate(
+ backend._parse_asn1_generalized_time(generalized_time)
+ )
def _decode_cert_issuer(backend, ext):
@@ -739,16 +771,24 @@ def _decode_cert_issuer(backend, ext):
backend._consume_errors()
raise ValueError(
"The {0} extension is corrupted and can't be parsed".format(
- CRLExtensionOID.CERTIFICATE_ISSUER))
+ CRLEntryExtensionOID.CERTIFICATE_ISSUER))
gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free)
- return x509.GeneralNames(_decode_general_names(backend, gns))
+ return x509.CertificateIssuer(_decode_general_names(backend, gns))
@utils.register_interface(x509.RevokedCertificate)
class _RevokedCertificate(object):
- def __init__(self, backend, x509_revoked):
+ def __init__(self, backend, crl, x509_revoked):
self._backend = backend
+ # The X509_REVOKED_value is a X509_REVOKED * that has
+ # no reference counting. This means when X509_CRL_free is
+ # called then the CRL and all X509_REVOKED * are freed. Since
+ # you can retain a reference to a single revoked certificate
+ # and let the CRL fall out of scope we need to retain a
+ # private reference to the CRL inside the RevokedCertificate
+ # object to prevent the gc from being called inappropriately.
+ self._crl = crl
self._x509_revoked = x509_revoked
@property
@@ -853,26 +893,34 @@ class _CertificateRevocationList(object):
self._backend.openssl_assert(res == 1)
return self._backend._read_mem_bio(bio)
- def _revoked_certificates(self):
+ def _revoked_cert(self, idx):
revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl)
- revoked_list = []
- if revoked != self._backend._ffi.NULL:
- num = self._backend._lib.sk_X509_REVOKED_num(revoked)
- for i in range(num):
- r = self._backend._lib.sk_X509_REVOKED_value(revoked, i)
- self._backend.openssl_assert(r != self._backend._ffi.NULL)
- revoked_list.append(_RevokedCertificate(self._backend, r))
-
- return revoked_list
+ r = self._backend._lib.sk_X509_REVOKED_value(revoked, idx)
+ self._backend.openssl_assert(r != self._backend._ffi.NULL)
+ return _RevokedCertificate(self._backend, self, r)
def __iter__(self):
- return iter(self._revoked_certificates())
+ for i in range(len(self)):
+ yield self._revoked_cert(i)
def __getitem__(self, idx):
- return self._revoked_certificates()[idx]
+ if isinstance(idx, slice):
+ start, stop, step = idx.indices(len(self))
+ return [self._revoked_cert(i) for i in range(start, stop, step)]
+ else:
+ idx = operator.index(idx)
+ if idx < 0:
+ idx += len(self)
+ if not 0 <= idx < len(self):
+ raise IndexError
+ return self._revoked_cert(idx)
def __len__(self):
- return len(self._revoked_certificates())
+ revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl)
+ if revoked == self._backend._ffi.NULL:
+ return 0
+ else:
+ return self._backend._lib.sk_X509_REVOKED_num(revoked)
@property
def extensions(self):
@@ -975,13 +1023,13 @@ _EXTENSION_HANDLERS = {
}
_REVOKED_EXTENSION_HANDLERS = {
- CRLExtensionOID.CRL_REASON: _decode_crl_reason,
- CRLExtensionOID.INVALIDITY_DATE: _decode_invalidity_date,
- CRLExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer,
+ CRLEntryExtensionOID.CRL_REASON: _decode_crl_reason,
+ CRLEntryExtensionOID.INVALIDITY_DATE: _decode_invalidity_date,
+ CRLEntryExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer,
}
_REVOKED_UNSUPPORTED_EXTENSIONS = set([
- CRLExtensionOID.CERTIFICATE_ISSUER,
+ CRLEntryExtensionOID.CERTIFICATE_ISSUER,
])
_CRL_EXTENSION_HANDLERS = {
diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py
index 07b6b9ac..8e419439 100644
--- a/src/cryptography/hazmat/bindings/openssl/binding.py
+++ b/src/cryptography/hazmat/bindings/openssl/binding.py
@@ -8,6 +8,7 @@ import collections
import os
import threading
import types
+import warnings
from cryptography.exceptions import InternalError
from cryptography.hazmat.bindings._openssl import ffi, lib
@@ -180,3 +181,11 @@ class Binding(object):
# condition registering the OpenSSL locks. On Python 3.4+ the import lock
# is per module so this approach will not work.
Binding.init_static_locks()
+
+if Binding.lib.SSLeay() < 0x10001000:
+ warnings.warn(
+ "OpenSSL versions less than 1.0.1 are no longer supported by the "
+ "OpenSSL project, please upgrade. A future version of cryptography "
+ "will drop support for these versions.",
+ DeprecationWarning
+ )
diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py
index 4449e85a..b85d50d3 100644
--- a/src/cryptography/utils.py
+++ b/src/cryptography/utils.py
@@ -12,7 +12,10 @@ import sys
import warnings
+# the functions deprecated in 1.0 are on an arbitrarily extended deprecation
+# cycle and should not be removed until we agree on when that cycle ends.
DeprecatedIn10 = DeprecationWarning
+DeprecatedIn12 = PendingDeprecationWarning
def read_only_property(name):
diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py
index c4434fd1..a1deb7f4 100644
--- a/src/cryptography/x509/__init__.py
+++ b/src/cryptography/x509/__init__.py
@@ -6,20 +6,22 @@ from __future__ import absolute_import, division, print_function
from cryptography.x509.base import (
Certificate, CertificateBuilder, CertificateRevocationList,
+ CertificateRevocationListBuilder,
CertificateSigningRequest, CertificateSigningRequestBuilder,
- InvalidVersion, RevokedCertificate,
+ InvalidVersion, RevokedCertificate, RevokedCertificateBuilder,
Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr,
load_pem_x509_certificate, load_pem_x509_crl, load_pem_x509_csr,
)
from cryptography.x509.extensions import (
AccessDescription, AuthorityInformationAccess,
AuthorityKeyIdentifier, BasicConstraints, CRLDistributionPoints,
- CRLNumber, CertificatePolicies, DistributionPoint, DuplicateExtension,
- ExtendedKeyUsage, Extension, ExtensionNotFound, ExtensionType, Extensions,
- GeneralNames, InhibitAnyPolicy, IssuerAlternativeName, KeyUsage,
+ CRLNumber, CRLReason, CertificateIssuer, CertificatePolicies,
+ DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension,
+ ExtensionNotFound, ExtensionType, Extensions, GeneralNames,
+ InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage,
NameConstraints, NoticeReference, OCSPNoCheck, PolicyInformation,
ReasonFlags, SubjectAlternativeName, SubjectKeyIdentifier,
- UnsupportedExtension, UserNotice
+ UnrecognizedExtension, UnsupportedExtension, UserNotice
)
from cryptography.x509.general_name import (
DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name,
@@ -28,9 +30,9 @@ from cryptography.x509.general_name import (
)
from cryptography.x509.name import Name, NameAttribute
from cryptography.x509.oid import (
- AuthorityInformationAccessOID, CRLExtensionOID, CertificatePoliciesOID,
- ExtendedKeyUsageOID, ExtensionOID, NameOID, ObjectIdentifier,
- SignatureAlgorithmOID, _SIG_OIDS_TO_HASH
+ AuthorityInformationAccessOID, CRLEntryExtensionOID, CRLExtensionOID,
+ CertificatePoliciesOID, ExtendedKeyUsageOID, ExtensionOID, NameOID,
+ ObjectIdentifier, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH
)
@@ -95,9 +97,9 @@ OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY
OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER
OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE
-OID_CERTIFICATE_ISSUER = CRLExtensionOID.CERTIFICATE_ISSUER
-OID_CRL_REASON = CRLExtensionOID.CRL_REASON
-OID_INVALIDITY_DATE = CRLExtensionOID.INVALIDITY_DATE
+OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER
+OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON
+OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE
OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS
OID_OCSP = AuthorityInformationAccessOID.OCSP
@@ -152,8 +154,10 @@ __all__ = [
"OtherName",
"Certificate",
"CertificateRevocationList",
+ "CertificateRevocationListBuilder",
"CertificateSigningRequest",
"RevokedCertificate",
+ "RevokedCertificateBuilder",
"CertificateSigningRequestBuilder",
"CertificateBuilder",
"Version",
@@ -161,4 +165,9 @@ __all__ = [
"OID_CA_ISSUERS",
"OID_OCSP",
"_GENERAL_NAMES",
+ "CRLExtensionOID",
+ "CertificateIssuer",
+ "CRLReason",
+ "InvalidityDate",
+ "UnrecognizedExtension",
]
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
index 057d0e9b..55e965f7 100644
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -518,3 +518,159 @@ class CertificateBuilder(object):
raise ValueError("A certificate must have a public key")
return backend.create_x509_certificate(self, private_key, algorithm)
+
+
+class CertificateRevocationListBuilder(object):
+ def __init__(self, issuer_name=None, last_update=None, next_update=None,
+ extensions=[], revoked_certificates=[]):
+ self._issuer_name = issuer_name
+ self._last_update = last_update
+ self._next_update = next_update
+ self._extensions = extensions
+ self._revoked_certificates = revoked_certificates
+
+ def issuer_name(self, issuer_name):
+ if not isinstance(issuer_name, Name):
+ raise TypeError('Expecting x509.Name object.')
+ if self._issuer_name is not None:
+ raise ValueError('The issuer name may only be set once.')
+ return CertificateRevocationListBuilder(
+ issuer_name, self._last_update, self._next_update,
+ self._extensions, self._revoked_certificates
+ )
+
+ def last_update(self, last_update):
+ if not isinstance(last_update, datetime.datetime):
+ raise TypeError('Expecting datetime object.')
+ if self._last_update is not None:
+ raise ValueError('Last update may only be set once.')
+ if last_update <= _UNIX_EPOCH:
+ raise ValueError('The last update date must be after the unix'
+ ' epoch (1970 January 1).')
+ if self._next_update is not None and last_update > self._next_update:
+ raise ValueError(
+ 'The last update date must be before the next update date.'
+ )
+ return CertificateRevocationListBuilder(
+ self._issuer_name, last_update, self._next_update,
+ self._extensions, self._revoked_certificates
+ )
+
+ def next_update(self, next_update):
+ if not isinstance(next_update, datetime.datetime):
+ raise TypeError('Expecting datetime object.')
+ if self._next_update is not None:
+ raise ValueError('Last update may only be set once.')
+ if next_update <= _UNIX_EPOCH:
+ raise ValueError('The last update date must be after the unix'
+ ' epoch (1970 January 1).')
+ if self._last_update is not None and next_update < self._last_update:
+ raise ValueError(
+ 'The next update date must be after the last update date.'
+ )
+ return CertificateRevocationListBuilder(
+ self._issuer_name, self._last_update, next_update,
+ self._extensions, self._revoked_certificates
+ )
+
+ def add_extension(self, extension, critical):
+ """
+ Adds an X.509 extension to the certificate revocation list.
+ """
+ if not isinstance(extension, ExtensionType):
+ raise TypeError("extension must be an ExtensionType")
+
+ extension = Extension(extension.oid, critical, 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 CertificateRevocationListBuilder(
+ self._issuer_name, self._last_update, self._next_update,
+ self._extensions + [extension], self._revoked_certificates
+ )
+
+ def add_revoked_certificate(self, revoked_certificate):
+ """
+ Adds a revoked certificate to the CRL.
+ """
+ if not isinstance(revoked_certificate, RevokedCertificate):
+ raise TypeError("Must be an instance of RevokedCertificate")
+
+ return CertificateRevocationListBuilder(
+ self._issuer_name, self._last_update,
+ self._next_update, self._extensions,
+ self._revoked_certificates + [revoked_certificate]
+ )
+
+ def sign(self, private_key, algorithm, backend):
+ if self._issuer_name is None:
+ raise ValueError("A CRL must have an issuer name")
+
+ if self._last_update is None:
+ raise ValueError("A CRL must have a last update time")
+
+ if self._next_update is None:
+ raise ValueError("A CRL must have a next update time")
+
+ return backend.create_x509_crl(self, private_key, algorithm)
+
+
+class RevokedCertificateBuilder(object):
+ def __init__(self, serial_number=None, revocation_date=None,
+ extensions=[]):
+ self._serial_number = serial_number
+ self._revocation_date = revocation_date
+ self._extensions = extensions
+
+ def serial_number(self, number):
+ if not isinstance(number, six.integer_types):
+ raise TypeError('Serial number must be of integral type.')
+ if self._serial_number is not None:
+ raise ValueError('The serial number may only be set once.')
+ if number < 0:
+ raise ValueError('The serial number should be non-negative.')
+ if utils.bit_length(number) > 160: # As defined in RFC 5280
+ raise ValueError('The serial number should not be more than 160 '
+ 'bits.')
+ return RevokedCertificateBuilder(
+ number, self._revocation_date, self._extensions
+ )
+
+ def revocation_date(self, time):
+ if not isinstance(time, datetime.datetime):
+ raise TypeError('Expecting datetime object.')
+ if self._revocation_date is not None:
+ raise ValueError('The revocation date may only be set once.')
+ if time <= _UNIX_EPOCH:
+ raise ValueError('The revocation date must be after the unix'
+ ' epoch (1970 January 1).')
+ return RevokedCertificateBuilder(
+ self._serial_number, time, self._extensions
+ )
+
+ def add_extension(self, extension, critical):
+ if not isinstance(extension, ExtensionType):
+ raise TypeError("extension must be an ExtensionType")
+
+ extension = Extension(extension.oid, critical, 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 RevokedCertificateBuilder(
+ self._serial_number, self._revocation_date,
+ self._extensions + [extension]
+ )
+
+ def build(self, backend):
+ if self._serial_number is None:
+ raise ValueError("A revoked certificate must have a serial number")
+ if self._revocation_date is None:
+ raise ValueError(
+ "A revoked certificate must have a revocation date"
+ )
+
+ return backend.create_x509_revoked_certificate(self)
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
index 15feb717..f7b5d7f5 100644
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function
import abc
+import datetime
import hashlib
import ipaddress
from enum import Enum
@@ -18,7 +19,9 @@ from cryptography import utils
from cryptography.hazmat.primitives import constant_time, serialization
from cryptography.x509.general_name import GeneralName, IPAddress, OtherName
from cryptography.x509.name import Name
-from cryptography.x509.oid import ExtensionOID, ObjectIdentifier
+from cryptography.x509.oid import (
+ CRLEntryExtensionOID, ExtensionOID, ObjectIdentifier
+)
class _SubjectPublicKeyInfo(univ.Sequence):
@@ -88,6 +91,13 @@ class Extensions(object):
raise ExtensionNotFound("No {0} extension was found".format(oid), oid)
def get_extension_for_class(self, extclass):
+ if extclass is UnrecognizedExtension:
+ raise TypeError(
+ "UnrecognizedExtension can't be used with "
+ "get_extension_for_class because more than one instance of the"
+ " class may be present."
+ )
+
for ext in self:
if isinstance(ext.value, extclass):
return ext
@@ -102,6 +112,9 @@ class Extensions(object):
def __len__(self):
return len(self._extensions)
+ def __getitem__(self, idx):
+ return self._extensions[idx]
+
def __repr__(self):
return (
"<Extensions({0})>".format(self._extensions)
@@ -127,6 +140,9 @@ class CRLNumber(object):
def __ne__(self, other):
return not self == other
+ def __hash__(self):
+ return hash(self.crl_number)
+
def __repr__(self):
return "<CRLNumber({0})>".format(self.crl_number)
@@ -226,6 +242,9 @@ class SubjectKeyIdentifier(object):
def __ne__(self, other):
return not self == other
+ def __hash__(self):
+ return hash(self.digest)
+
@utils.register_interface(ExtensionType)
class AuthorityInformationAccess(object):
@@ -258,6 +277,9 @@ class AuthorityInformationAccess(object):
def __ne__(self, other):
return not self == other
+ def __getitem__(self, idx):
+ return self._descriptions[idx]
+
class AccessDescription(object):
def __init__(self, access_method, access_location):
@@ -330,6 +352,9 @@ class BasicConstraints(object):
def __ne__(self, other):
return not self == other
+ def __hash__(self):
+ return hash((self.ca, self.path_length))
+
@utils.register_interface(ExtensionType)
class CRLDistributionPoints(object):
@@ -364,6 +389,9 @@ class CRLDistributionPoints(object):
def __ne__(self, other):
return not self == other
+ def __getitem__(self, idx):
+ return self._distribution_points[idx]
+
class DistributionPoint(object):
def __init__(self, full_name, relative_name, reasons, crl_issuer):
@@ -486,6 +514,9 @@ class CertificatePolicies(object):
def __ne__(self, other):
return not self == other
+ def __getitem__(self, idx):
+ return self._policies[idx]
+
class PolicyInformation(object):
def __init__(self, policy_identifier, policy_qualifiers):
@@ -885,6 +916,9 @@ class GeneralNames(object):
def __ne__(self, other):
return not self == other
+ def __getitem__(self, idx):
+ return self._general_names[idx]
+
@utils.register_interface(ExtensionType)
class SubjectAlternativeName(object):
@@ -911,6 +945,9 @@ class SubjectAlternativeName(object):
return self._general_names == other._general_names
+ def __getitem__(self, idx):
+ return self._general_names[idx]
+
def __ne__(self, other):
return not self == other
@@ -942,3 +979,127 @@ class IssuerAlternativeName(object):
def __ne__(self, other):
return not self == other
+
+ def __getitem__(self, idx):
+ return self._general_names[idx]
+
+
+@utils.register_interface(ExtensionType)
+class CertificateIssuer(object):
+ oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER
+
+ def __init__(self, general_names):
+ self._general_names = GeneralNames(general_names)
+
+ def __iter__(self):
+ return iter(self._general_names)
+
+ def __len__(self):
+ return len(self._general_names)
+
+ def get_values_for_type(self, type):
+ return self._general_names.get_values_for_type(type)
+
+ def __repr__(self):
+ return "<CertificateIssuer({0})>".format(self._general_names)
+
+ def __eq__(self, other):
+ if not isinstance(other, CertificateIssuer):
+ return NotImplemented
+
+ return self._general_names == other._general_names
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __getitem__(self, idx):
+ return self._general_names[idx]
+
+
+@utils.register_interface(ExtensionType)
+class CRLReason(object):
+ oid = CRLEntryExtensionOID.CRL_REASON
+
+ def __init__(self, reason):
+ if not isinstance(reason, ReasonFlags):
+ raise TypeError("reason must be an element from ReasonFlags")
+
+ self._reason = reason
+
+ def __repr__(self):
+ return "<CRLReason(reason={0})>".format(self._reason)
+
+ def __eq__(self, other):
+ if not isinstance(other, CRLReason):
+ return NotImplemented
+
+ return self.reason == other.reason
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash(self.reason)
+
+ reason = utils.read_only_property("_reason")
+
+
+@utils.register_interface(ExtensionType)
+class InvalidityDate(object):
+ oid = CRLEntryExtensionOID.INVALIDITY_DATE
+
+ def __init__(self, invalidity_date):
+ if not isinstance(invalidity_date, datetime.datetime):
+ raise TypeError("invalidity_date must be a datetime.datetime")
+
+ self._invalidity_date = invalidity_date
+
+ def __repr__(self):
+ return "<InvalidityDate(invalidity_date={0})>".format(
+ self._invalidity_date
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, InvalidityDate):
+ return NotImplemented
+
+ return self.invalidity_date == other.invalidity_date
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash(self.invalidity_date)
+
+ invalidity_date = utils.read_only_property("_invalidity_date")
+
+
+@utils.register_interface(ExtensionType)
+class UnrecognizedExtension(object):
+ def __init__(self, oid, value):
+ if not isinstance(oid, ObjectIdentifier):
+ raise TypeError("oid must be an ObjectIdentifier")
+ self._oid = oid
+ self._value = value
+
+ oid = utils.read_only_property("_oid")
+ value = utils.read_only_property("_value")
+
+ def __repr__(self):
+ return (
+ "<UnrecognizedExtension(oid={0.oid}, value={0.value!r})>".format(
+ self
+ )
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, UnrecognizedExtension):
+ return NotImplemented
+
+ return self.oid == other.oid and self.value == other.value
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash((self.oid, self.value))
diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py
index 94295d66..ced15103 100644
--- a/src/cryptography/x509/oid.py
+++ b/src/cryptography/x509/oid.py
@@ -88,12 +88,20 @@ class ExtensionOID(object):
CRL_NUMBER = ObjectIdentifier("2.5.29.20")
-class CRLExtensionOID(object):
+class CRLEntryExtensionOID(object):
CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29")
CRL_REASON = ObjectIdentifier("2.5.29.21")
INVALIDITY_DATE = ObjectIdentifier("2.5.29.24")
+CRLExtensionOID = utils.deprecated(
+ CRLEntryExtensionOID,
+ __name__,
+ "CRLExtensionOID has been renamed to CRLEntryExtensionOID",
+ utils.DeprecatedIn12
+)
+
+
class NameOID(object):
COMMON_NAME = ObjectIdentifier("2.5.4.3")
COUNTRY_NAME = ObjectIdentifier("2.5.4.6")
@@ -220,9 +228,9 @@ _OID_NAMES = {
ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName",
ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName",
ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints",
- CRLExtensionOID.CRL_REASON: "cRLReason",
- CRLExtensionOID.INVALIDITY_DATE: "invalidityDate",
- CRLExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer",
+ CRLEntryExtensionOID.CRL_REASON: "cRLReason",
+ CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate",
+ CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer",
ExtensionOID.NAME_CONSTRAINTS: "nameConstraints",
ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints",
ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies",
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
index 81a64ce0..74835716 100644
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -218,6 +218,12 @@ class DummyX509Backend(object):
def create_x509_certificate(self, builder, private_key, algorithm):
pass
+ def create_x509_crl(self, builder, private_key, algorithm):
+ pass
+
+ def create_x509_revoked_certificate(self, builder):
+ pass
+
class TestMultiBackend(object):
def test_ciphers(self):
@@ -514,6 +520,8 @@ class TestMultiBackend(object):
backend.load_der_x509_csr(b"reqdata")
backend.create_x509_csr(object(), b"privatekey", hashes.SHA1())
backend.create_x509_certificate(object(), b"privatekey", hashes.SHA1())
+ backend.create_x509_crl(object(), b"privatekey", hashes.SHA1())
+ backend.create_x509_revoked_certificate(object())
backend = MultiBackend([])
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
@@ -534,3 +542,9 @@ class TestMultiBackend(object):
backend.create_x509_certificate(
object(), b"privatekey", hashes.SHA1()
)
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
+ backend.create_x509_crl(
+ object(), b"privatekey", hashes.SHA1()
+ )
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
+ backend.create_x509_revoked_certificate(object())
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index d048fe68..ad2daf7d 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -4,6 +4,7 @@
from __future__ import absolute_import, division, print_function
+import datetime
import os
import subprocess
import sys
@@ -13,7 +14,7 @@ import pretend
import pytest
-from cryptography import utils
+from cryptography import utils, x509
from cryptography.exceptions import InternalError, _Reasons
from cryptography.hazmat.backends.interfaces import RSABackend
from cryptography.hazmat.backends.openssl.backend import (
@@ -500,8 +501,108 @@ class TestOpenSSLSignX509Certificate(object):
with pytest.raises(TypeError):
backend.create_x509_certificate(object(), private_key, DummyHash())
+ @pytest.mark.skipif(
+ backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000,
+ reason="Requires an older OpenSSL. Must be < 1.0.1"
+ )
+ def test_sign_with_dsa_private_key_is_unsupported(self):
+ private_key = DSA_KEY_2048.private_key(backend)
+ builder = x509.CertificateBuilder()
+ builder = builder.subject_name(
+ x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')])
+ ).issuer_name(
+ x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')])
+ ).serial_number(
+ 1
+ ).public_key(
+ private_key.public_key()
+ ).not_valid_before(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ ).not_valid_after(
+ datetime.datetime(2032, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(NotImplementedError):
+ builder.sign(private_key, hashes.SHA512(), backend)
+
+ @pytest.mark.skipif(
+ backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000,
+ reason="Requires an older OpenSSL. Must be < 1.0.1"
+ )
+ def test_sign_with_ec_private_key_is_unsupported(self):
+ _skip_curve_unsupported(backend, ec.SECP256R1())
+ private_key = ec.generate_private_key(ec.SECP256R1(), backend)
+ builder = x509.CertificateBuilder()
+ builder = builder.subject_name(
+ x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')])
+ ).issuer_name(
+ x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')])
+ ).serial_number(
+ 1
+ ).public_key(
+ private_key.public_key()
+ ).not_valid_before(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ ).not_valid_after(
+ datetime.datetime(2032, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(NotImplementedError):
+ builder.sign(private_key, hashes.SHA512(), backend)
+
+
+class TestOpenSSLSignX509CertificateRevocationList(object):
+ def test_invalid_builder(self):
+ private_key = RSA_KEY_2048.private_key(backend)
+
+ with pytest.raises(TypeError):
+ backend.create_x509_crl(object(), private_key, hashes.SHA256())
+
+ @pytest.mark.skipif(
+ backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000,
+ reason="Requires an older OpenSSL. Must be < 1.0.1"
+ )
+ def test_sign_with_dsa_private_key_is_unsupported(self):
+ private_key = DSA_KEY_2048.private_key(backend)
+ builder = x509.CertificateRevocationListBuilder()
+ builder = builder.issuer_name(
+ x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')])
+ ).last_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ ).next_update(
+ datetime.datetime(2032, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(NotImplementedError):
+ builder.sign(private_key, hashes.SHA1(), backend)
+
+ @pytest.mark.skipif(
+ backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000,
+ reason="Requires an older OpenSSL. Must be < 1.0.1"
+ )
+ def test_sign_with_ec_private_key_is_unsupported(self):
+ _skip_curve_unsupported(backend, ec.SECP256R1())
+ private_key = ec.generate_private_key(ec.SECP256R1(), backend)
+ builder = x509.CertificateRevocationListBuilder()
+ builder = builder.issuer_name(
+ x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')])
+ ).last_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ ).next_update(
+ datetime.datetime(2032, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(NotImplementedError):
+ builder.sign(private_key, hashes.SHA512(), backend)
+
+
+class TestOpenSSLCreateRevokedCertificate(object):
+ def test_invalid_builder(self):
+ with pytest.raises(TypeError):
+ backend.create_x509_revoked_certificate(object())
+
-class TestOpenSSLSerialisationWithOpenSSL(object):
+class TestOpenSSLSerializationWithOpenSSL(object):
def test_pem_password_cb_buffer_too_small(self):
ffi_cb, userdata = backend._pem_password_cb(b"aa")
handle = backend._ffi.new_handle(userdata)
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 0b83fd65..b6213d6d 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1194,6 +1194,43 @@ class TestRSADecryption(object):
)
assert message == binascii.unhexlify(example["message"])
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.rsa_padding_supported(
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA1(),
+ label=None
+ )
+ ),
+ skip_message="Does not support OAEP."
+ )
+ def test_invalid_oaep_decryption(self, backend):
+ # More recent versions of OpenSSL may raise RSA_R_OAEP_DECODING_ERROR
+ # This test triggers it and confirms that we properly handle it. Other
+ # backends should also return the proper ValueError.
+ private_key = RSA_KEY_512.private_key(backend)
+
+ ciphertext = private_key.public_key().encrypt(
+ b'secure data',
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA1(),
+ label=None
+ )
+ )
+
+ private_key_alt = RSA_KEY_512_ALT.private_key(backend)
+
+ with pytest.raises(ValueError):
+ private_key_alt.decrypt(
+ ciphertext,
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA1(),
+ label=None
+ )
+ )
+
def test_unsupported_oaep_mgf(self, backend):
private_key = RSA_KEY_512.private_key(backend)
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF):
diff --git a/tests/test_x509.py b/tests/test_x509.py
index ae2746e3..6145edb1 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -173,6 +173,20 @@ class TestCertificateRevocationList(object):
# Check that len() works for CRLs.
assert len(crl) == 12
+ def test_revoked_cert_retrieval_retain_only_revoked(self, backend):
+ """
+ This test attempts to trigger the crash condition described in
+ https://github.com/pyca/cryptography/issues/2557
+ PyPy does gc at its own pace, so it will only be reliable on CPython.
+ """
+ revoked = _load_cert(
+ os.path.join("x509", "custom", "crl_all_reasons.pem"),
+ x509.load_pem_x509_crl,
+ backend
+ )[11]
+ assert revoked.revocation_date == datetime.datetime(2015, 1, 1, 0, 0)
+ assert revoked.serial_number == 11
+
def test_extensions(self, backend):
crl = _load_cert(
os.path.join("x509", "custom", "crl_ian_aia_aki.pem"),
@@ -318,7 +332,6 @@ class TestCertificateRevocationList(object):
@pytest.mark.requires_backend_interface(interface=X509Backend)
class TestRevokedCertificate(object):
-
def test_revoked_basics(self, backend):
crl = _load_cert(
os.path.join("x509", "custom", "crl_all_reasons.pem"),
@@ -342,12 +355,12 @@ class TestRevokedCertificate(object):
backend
)
- exp_issuer = x509.GeneralNames([
+ exp_issuer = [
x509.DirectoryName(x509.Name([
x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"),
x509.NameAttribute(x509.OID_COMMON_NAME, u"cryptography.io"),
]))
- ])
+ ]
# First revoked cert doesn't have extensions, test if it is handled
# correctly.
@@ -366,29 +379,28 @@ class TestRevokedCertificate(object):
rev1 = crl[1]
assert isinstance(rev1.extensions, x509.Extensions)
- reason = rev1.extensions.get_extension_for_oid(
- x509.OID_CRL_REASON).value
- assert reason == x509.ReasonFlags.unspecified
+ reason = rev1.extensions.get_extension_for_class(
+ x509.CRLReason).value
+ assert reason == x509.CRLReason(x509.ReasonFlags.unspecified)
- issuer = rev1.extensions.get_extension_for_oid(
- x509.OID_CERTIFICATE_ISSUER).value
- assert issuer == exp_issuer
+ issuer = rev1.extensions.get_extension_for_class(
+ x509.CertificateIssuer).value
+ assert issuer == x509.CertificateIssuer(exp_issuer)
- date = rev1.extensions.get_extension_for_oid(
- x509.OID_INVALIDITY_DATE).value
- assert isinstance(date, datetime.datetime)
- assert date.isoformat() == "2015-01-01T00:00:00"
+ date = rev1.extensions.get_extension_for_class(
+ x509.InvalidityDate).value
+ assert date == x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0))
# Check if all reason flags can be found in the CRL.
flags = set(x509.ReasonFlags)
for rev in crl:
try:
- r = rev.extensions.get_extension_for_oid(x509.OID_CRL_REASON)
+ r = rev.extensions.get_extension_for_class(x509.CRLReason)
except x509.ExtensionNotFound:
# Not all revoked certs have a reason extension.
pass
else:
- flags.discard(r.value)
+ flags.discard(r.value.reason)
assert len(flags) == 0
@@ -446,6 +458,23 @@ class TestRevokedCertificate(object):
with pytest.raises(ValueError):
crl[0].extensions
+ def test_indexing(self, backend):
+ crl = _load_cert(
+ os.path.join("x509", "custom", "crl_all_reasons.pem"),
+ x509.load_pem_x509_crl,
+ backend
+ )
+
+ with pytest.raises(IndexError):
+ crl[-13]
+ with pytest.raises(IndexError):
+ crl[12]
+
+ assert crl[-1].serial_number == crl[11].serial_number
+ assert len(crl[2:4]) == 2
+ assert crl[2:4][0].serial_number == crl[2].serial_number
+ assert crl[2:4][1].serial_number == crl[3].serial_number
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
@@ -1064,7 +1093,11 @@ class TestRSACertificateRequest(object):
backend
)
extensions = request.extensions
- assert len(extensions) == 0
+ assert len(extensions) == 1
+ assert extensions[0].oid == x509.ObjectIdentifier("1.2.3.4")
+ assert extensions[0].value == x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"value"
+ )
def test_request_basic_constraints(self, backend):
request = _load_cert(
@@ -1710,57 +1743,6 @@ class TestCertificateBuilder(object):
with pytest.raises(TypeError):
builder.sign(private_key, object(), backend)
- @pytest.mark.requires_backend_interface(interface=DSABackend)
- @pytest.mark.requires_backend_interface(interface=X509Backend)
- def test_sign_with_dsa_private_key_is_unsupported(self, backend):
- if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000:
- pytest.skip("Requires an older OpenSSL. Must be < 1.0.1")
-
- private_key = DSA_KEY_2048.private_key(backend)
- builder = x509.CertificateBuilder()
- builder = builder.subject_name(
- x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
- ).issuer_name(
- x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
- ).serial_number(
- 1
- ).public_key(
- private_key.public_key()
- ).not_valid_before(
- datetime.datetime(2002, 1, 1, 12, 1)
- ).not_valid_after(
- datetime.datetime(2032, 1, 1, 12, 1)
- )
-
- with pytest.raises(NotImplementedError):
- builder.sign(private_key, hashes.SHA512(), backend)
-
- @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
- @pytest.mark.requires_backend_interface(interface=X509Backend)
- def test_sign_with_ec_private_key_is_unsupported(self, backend):
- if backend._lib.OPENSSL_VERSION_NUMBER >= 0x10001000:
- pytest.skip("Requires an older OpenSSL. Must be < 1.0.1")
-
- _skip_curve_unsupported(backend, ec.SECP256R1())
- private_key = ec.generate_private_key(ec.SECP256R1(), backend)
- builder = x509.CertificateBuilder()
- builder = builder.subject_name(
- x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
- ).issuer_name(
- x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
- ).serial_number(
- 1
- ).public_key(
- private_key.public_key()
- ).not_valid_before(
- datetime.datetime(2002, 1, 1, 12, 1)
- ).not_valid_after(
- datetime.datetime(2032, 1, 1, 12, 1)
- )
-
- with pytest.raises(NotImplementedError):
- builder.sign(private_key, hashes.SHA512(), backend)
-
@pytest.mark.parametrize(
"cdp",
[
diff --git a/tests/test_x509_crlbuilder.py b/tests/test_x509_crlbuilder.py
new file mode 100644
index 00000000..32a07487
--- /dev/null
+++ b/tests/test_x509_crlbuilder.py
@@ -0,0 +1,449 @@
+# 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 datetime
+
+import pytest
+
+from cryptography import x509
+from cryptography.hazmat.backends.interfaces import (
+ DSABackend, EllipticCurveBackend, RSABackend, X509Backend
+)
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID
+
+from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048
+from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512
+from .hazmat.primitives.test_ec import _skip_curve_unsupported
+
+
+class TestCertificateRevocationListBuilder(object):
+ def test_issuer_name_invalid(self):
+ builder = x509.CertificateRevocationListBuilder()
+ with pytest.raises(TypeError):
+ builder.issuer_name("notanx509name")
+
+ def test_set_issuer_name_twice(self):
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
+ )
+ with pytest.raises(ValueError):
+ builder.issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
+ )
+
+ def test_last_update_invalid(self):
+ builder = x509.CertificateRevocationListBuilder()
+ with pytest.raises(TypeError):
+ builder.last_update("notadatetime")
+
+ def test_last_update_before_unix_epoch(self):
+ builder = x509.CertificateRevocationListBuilder()
+ with pytest.raises(ValueError):
+ builder.last_update(datetime.datetime(1960, 8, 10))
+
+ def test_set_last_update_twice(self):
+ builder = x509.CertificateRevocationListBuilder().last_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+ with pytest.raises(ValueError):
+ builder.last_update(datetime.datetime(2002, 1, 1, 12, 1))
+
+ def test_next_update_invalid(self):
+ builder = x509.CertificateRevocationListBuilder()
+ with pytest.raises(TypeError):
+ builder.next_update("notadatetime")
+
+ def test_next_update_before_unix_epoch(self):
+ builder = x509.CertificateRevocationListBuilder()
+ with pytest.raises(ValueError):
+ builder.next_update(datetime.datetime(1960, 8, 10))
+
+ def test_set_next_update_twice(self):
+ builder = x509.CertificateRevocationListBuilder().next_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+ with pytest.raises(ValueError):
+ builder.next_update(datetime.datetime(2002, 1, 1, 12, 1))
+
+ def test_last_update_after_next_update(self):
+ builder = x509.CertificateRevocationListBuilder()
+
+ builder = builder.next_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+ with pytest.raises(ValueError):
+ builder.last_update(datetime.datetime(2003, 1, 1, 12, 1))
+
+ def test_next_update_after_last_update(self):
+ builder = x509.CertificateRevocationListBuilder()
+
+ builder = builder.last_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+ with pytest.raises(ValueError):
+ builder.next_update(datetime.datetime(2001, 1, 1, 12, 1))
+
+ def test_add_extension_checks_for_duplicates(self):
+ builder = x509.CertificateRevocationListBuilder().add_extension(
+ x509.CRLNumber(1), False
+ )
+
+ with pytest.raises(ValueError):
+ builder.add_extension(x509.CRLNumber(2), False)
+
+ def test_add_invalid_extension(self):
+ builder = x509.CertificateRevocationListBuilder()
+
+ with pytest.raises(TypeError):
+ builder.add_extension(
+ object(), False
+ )
+
+ def test_add_invalid_revoked_certificate(self):
+ builder = x509.CertificateRevocationListBuilder()
+
+ with pytest.raises(TypeError):
+ builder.add_revoked_certificate(object())
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_issuer_name(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ builder = x509.CertificateRevocationListBuilder().last_update(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ ).next_update(
+ datetime.datetime(2030, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(ValueError):
+ builder.sign(private_key, hashes.SHA256(), backend)
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_last_update(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
+ ).next_update(
+ datetime.datetime(2030, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(ValueError):
+ builder.sign(private_key, hashes.SHA256(), backend)
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_next_update(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
+ ).last_update(
+ datetime.datetime(2030, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(ValueError):
+ builder.sign(private_key, hashes.SHA256(), backend)
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_empty_list(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(last_update).next_update(next_update)
+
+ crl = builder.sign(private_key, hashes.SHA256(), backend)
+ assert len(crl) == 0
+ assert crl.last_update == last_update
+ assert crl.next_update == next_update
+
+ @pytest.mark.parametrize(
+ "extension",
+ [
+ x509.CRLNumber(13),
+ x509.AuthorityKeyIdentifier(
+ b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08"
+ b"\xcbY",
+ None,
+ None
+ ),
+ x509.AuthorityInformationAccess([
+ x509.AccessDescription(
+ AuthorityInformationAccessOID.CA_ISSUERS,
+ x509.DNSName(u"cryptography.io")
+ )
+ ]),
+ x509.IssuerAlternativeName([
+ x509.UniformResourceIdentifier(u"https://cryptography.io"),
+ ])
+ ]
+ )
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_extensions(self, backend, extension):
+ private_key = RSA_KEY_2048.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ ).add_extension(
+ extension, False
+ )
+
+ crl = builder.sign(private_key, hashes.SHA256(), backend)
+ assert len(crl) == 0
+ assert len(crl.extensions) == 1
+ ext = crl.extensions.get_extension_for_class(type(extension))
+ assert ext.critical is False
+ assert ext.value == extension
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_multiple_extensions_critical(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ ian = x509.IssuerAlternativeName([
+ x509.UniformResourceIdentifier(u"https://cryptography.io"),
+ ])
+ crl_number = x509.CRLNumber(13)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ ).add_extension(
+ crl_number, False
+ ).add_extension(
+ ian, True
+ )
+
+ crl = builder.sign(private_key, hashes.SHA256(), backend)
+ assert len(crl) == 0
+ assert len(crl.extensions) == 2
+ ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber)
+ assert ext1.critical is False
+ assert ext1.value == crl_number
+ ext2 = crl.extensions.get_extension_for_class(
+ x509.IssuerAlternativeName
+ )
+ assert ext2.critical is True
+ assert ext2.value == ian
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_add_unsupported_extension(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ ).add_extension(
+ x509.OCSPNoCheck(), False
+ )
+ with pytest.raises(NotImplementedError):
+ builder.sign(private_key, hashes.SHA256(), backend)
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_rsa_key_too_small(self, backend):
+ private_key = RSA_KEY_512.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ )
+
+ with pytest.raises(ValueError):
+ builder.sign(private_key, hashes.SHA512(), backend)
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_with_invalid_hash(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ )
+
+ with pytest.raises(TypeError):
+ builder.sign(private_key, object(), backend)
+
+ @pytest.mark.requires_backend_interface(interface=DSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_dsa_key(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)
+ invalidity_date = x509.InvalidityDate(
+ datetime.datetime(2002, 1, 1, 0, 0)
+ )
+ ian = x509.IssuerAlternativeName([
+ x509.UniformResourceIdentifier(u"https://cryptography.io"),
+ ])
+ revoked_cert0 = x509.RevokedCertificateBuilder().serial_number(
+ 2
+ ).revocation_date(
+ datetime.datetime(2012, 1, 1, 1, 1)
+ ).add_extension(
+ invalidity_date, False
+ ).build(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ ).add_revoked_certificate(
+ revoked_cert0
+ ).add_extension(
+ ian, False
+ )
+
+ crl = builder.sign(private_key, hashes.SHA256(), backend)
+ assert crl.extensions.get_extension_for_class(
+ x509.IssuerAlternativeName
+ ).value == ian
+ assert crl[0].serial_number == revoked_cert0.serial_number
+ assert crl[0].revocation_date == revoked_cert0.revocation_date
+ assert len(crl[0].extensions) == 1
+ ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
+ assert ext.critical is False
+ assert ext.value == invalidity_date
+
+ @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_ec_key_unsupported(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)
+ invalidity_date = x509.InvalidityDate(
+ datetime.datetime(2002, 1, 1, 0, 0)
+ )
+ ian = x509.IssuerAlternativeName([
+ x509.UniformResourceIdentifier(u"https://cryptography.io"),
+ ])
+ revoked_cert0 = x509.RevokedCertificateBuilder().serial_number(
+ 2
+ ).revocation_date(
+ datetime.datetime(2012, 1, 1, 1, 1)
+ ).add_extension(
+ invalidity_date, False
+ ).build(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ ).add_revoked_certificate(
+ revoked_cert0
+ ).add_extension(
+ ian, False
+ )
+
+ crl = builder.sign(private_key, hashes.SHA256(), backend)
+ assert crl.extensions.get_extension_for_class(
+ x509.IssuerAlternativeName
+ ).value == ian
+ assert crl[0].serial_number == revoked_cert0.serial_number
+ assert crl[0].revocation_date == revoked_cert0.revocation_date
+ assert len(crl[0].extensions) == 1
+ ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
+ assert ext.critical is False
+ assert ext.value == invalidity_date
+
+ @pytest.mark.requires_backend_interface(interface=RSABackend)
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_sign_with_revoked_certificates(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+ last_update = datetime.datetime(2002, 1, 1, 12, 1)
+ next_update = datetime.datetime(2030, 1, 1, 12, 1)
+ invalidity_date = x509.InvalidityDate(
+ datetime.datetime(2002, 1, 1, 0, 0)
+ )
+ revoked_cert0 = x509.RevokedCertificateBuilder().serial_number(
+ 38
+ ).revocation_date(
+ datetime.datetime(2011, 1, 1, 1, 1)
+ ).build(backend)
+ revoked_cert1 = x509.RevokedCertificateBuilder().serial_number(
+ 2
+ ).revocation_date(
+ datetime.datetime(2012, 1, 1, 1, 1)
+ ).add_extension(
+ invalidity_date, False
+ ).build(backend)
+ builder = x509.CertificateRevocationListBuilder().issuer_name(
+ x509.Name([
+ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
+ ])
+ ).last_update(
+ last_update
+ ).next_update(
+ next_update
+ ).add_revoked_certificate(
+ revoked_cert0
+ ).add_revoked_certificate(
+ revoked_cert1
+ )
+
+ crl = builder.sign(private_key, hashes.SHA256(), backend)
+ assert len(crl) == 2
+ assert crl.last_update == last_update
+ assert crl.next_update == next_update
+ assert crl[0].serial_number == revoked_cert0.serial_number
+ assert crl[0].revocation_date == revoked_cert0.revocation_date
+ assert len(crl[0].extensions) == 0
+ assert crl[1].serial_number == revoked_cert1.serial_number
+ assert crl[1].revocation_date == revoked_cert1.revocation_date
+ assert len(crl[1].extensions) == 1
+ ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate)
+ assert ext.critical is False
+ assert ext.value == invalidity_date
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index d9743c8e..03a3730a 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function
import binascii
+import datetime
import ipaddress
import os
@@ -74,6 +75,173 @@ class TestExtension(object):
assert ext1 != object()
+class TestUnrecognizedExtension(object):
+ def test_invalid_oid(self):
+ with pytest.raises(TypeError):
+ x509.UnrecognizedExtension("notanoid", b"somedata")
+
+ def test_eq(self):
+ ext1 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01"
+ )
+ ext2 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01"
+ )
+ assert ext1 == ext2
+
+ def test_ne(self):
+ ext1 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01"
+ )
+ ext2 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x02"
+ )
+ ext3 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01"
+ )
+ assert ext1 != ext2
+ assert ext1 != ext3
+ assert ext1 != object()
+
+ def test_repr(self):
+ ext1 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01"
+ )
+ if six.PY3:
+ assert repr(ext1) == (
+ "<UnrecognizedExtension(oid=<ObjectIdentifier(oid=1.2.3.4, "
+ "name=Unknown OID)>, value=b'\\x03\\x02\\x01')>"
+ )
+ else:
+ assert repr(ext1) == (
+ "<UnrecognizedExtension(oid=<ObjectIdentifier(oid=1.2.3.4, "
+ "name=Unknown OID)>, value='\\x03\\x02\\x01')>"
+ )
+
+ def test_hash(self):
+ ext1 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01"
+ )
+ ext2 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01"
+ )
+ ext3 = x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01"
+ )
+ assert hash(ext1) == hash(ext2)
+ assert hash(ext1) != hash(ext3)
+
+
+class TestCertificateIssuer(object):
+ def test_iter_names(self):
+ ci = x509.CertificateIssuer([
+ x509.DNSName(u"cryptography.io"),
+ x509.DNSName(u"crypto.local"),
+ ])
+ assert len(ci) == 2
+ assert list(ci) == [
+ x509.DNSName(u"cryptography.io"),
+ x509.DNSName(u"crypto.local"),
+ ]
+
+ def test_indexing(self):
+ ci = x509.CertificateIssuer([
+ x509.DNSName(u"cryptography.io"),
+ x509.DNSName(u"crypto.local"),
+ x509.DNSName(u"another.local"),
+ x509.RFC822Name(u"email@another.local"),
+ x509.UniformResourceIdentifier(u"http://another.local"),
+ ])
+ assert ci[-1] == ci[4]
+ assert ci[2:6:2] == [ci[2], ci[4]]
+
+ def test_eq(self):
+ ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
+ ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
+ assert ci1 == ci2
+
+ def test_ne(self):
+ ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
+ ci2 = x509.CertificateIssuer([x509.DNSName(u"somethingelse.tld")])
+ assert ci1 != ci2
+ assert ci1 != object()
+
+ def test_repr(self):
+ ci = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")])
+ assert repr(ci) == (
+ "<CertificateIssuer(<GeneralNames([<DNSName(value=cryptography.io"
+ ")>])>)>"
+ )
+
+ def test_get_values_for_type(self):
+ ci = x509.CertificateIssuer(
+ [x509.DNSName(u"cryptography.io")]
+ )
+ names = ci.get_values_for_type(x509.DNSName)
+ assert names == [u"cryptography.io"]
+
+
+class TestCRLReason(object):
+ def test_invalid_reason_flags(self):
+ with pytest.raises(TypeError):
+ x509.CRLReason("notareason")
+
+ def test_eq(self):
+ reason1 = x509.CRLReason(x509.ReasonFlags.unspecified)
+ reason2 = x509.CRLReason(x509.ReasonFlags.unspecified)
+ assert reason1 == reason2
+
+ def test_ne(self):
+ reason1 = x509.CRLReason(x509.ReasonFlags.unspecified)
+ reason2 = x509.CRLReason(x509.ReasonFlags.ca_compromise)
+ assert reason1 != reason2
+ assert reason1 != object()
+
+ def test_hash(self):
+ reason1 = x509.CRLReason(x509.ReasonFlags.unspecified)
+ reason2 = x509.CRLReason(x509.ReasonFlags.unspecified)
+ reason3 = x509.CRLReason(x509.ReasonFlags.ca_compromise)
+
+ assert hash(reason1) == hash(reason2)
+ assert hash(reason1) != hash(reason3)
+
+ def test_repr(self):
+ reason1 = x509.CRLReason(x509.ReasonFlags.unspecified)
+ assert repr(reason1) == (
+ "<CRLReason(reason=ReasonFlags.unspecified)>"
+ )
+
+
+class TestInvalidityDate(object):
+ def test_invalid_invalidity_date(self):
+ with pytest.raises(TypeError):
+ x509.InvalidityDate("notadate")
+
+ def test_eq(self):
+ invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1))
+ invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1))
+ assert invalid1 == invalid2
+
+ def test_ne(self):
+ invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1))
+ invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2))
+ assert invalid1 != invalid2
+ assert invalid1 != object()
+
+ def test_repr(self):
+ invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1))
+ assert repr(invalid1) == (
+ "<InvalidityDate(invalidity_date=2015-01-01 01:01:00)>"
+ )
+
+ def test_hash(self):
+ invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1))
+ invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1))
+ invalid3 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2))
+ assert hash(invalid1) == hash(invalid2)
+ assert hash(invalid1) != hash(invalid3)
+
+
class TestNoticeReference(object):
def test_notice_numbers_not_all_int(self):
with pytest.raises(TypeError):
@@ -269,6 +437,16 @@ class TestCertificatePolicies(object):
assert cp != cp2
assert cp != object()
+ def test_indexing(self):
+ pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"test"])
+ pi2 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.4"), [u"test"])
+ pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.5"), [u"test"])
+ pi4 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.6"), [u"test"])
+ pi5 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.7"), [u"test"])
+ cp = x509.CertificatePolicies([pi, pi2, pi3, pi4, pi5])
+ assert cp[-1] == cp[4]
+ assert cp[2:6:2] == [cp[2], cp[4]]
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
@@ -595,6 +773,20 @@ class TestSubjectKeyIdentifier(object):
assert ski != ski2
assert ski != object()
+ def test_hash(self):
+ ski1 = x509.SubjectKeyIdentifier(
+ binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+ )
+ ski2 = x509.SubjectKeyIdentifier(
+ binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9")
+ )
+ ski3 = x509.SubjectKeyIdentifier(
+ binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980")
+ )
+
+ assert hash(ski1) == hash(ski2)
+ assert hash(ski1) != hash(ski3)
+
class TestAuthorityKeyIdentifier(object):
def test_authority_cert_issuer_not_generalname(self):
@@ -720,6 +912,13 @@ class TestBasicConstraints(object):
"<BasicConstraints(ca=True, path_length=None)>"
)
+ def test_hash(self):
+ na = x509.BasicConstraints(ca=True, path_length=None)
+ na2 = x509.BasicConstraints(ca=True, path_length=None)
+ na3 = x509.BasicConstraints(ca=True, path_length=0)
+ assert hash(na) == hash(na2)
+ assert hash(na) != hash(na3)
+
def test_eq(self):
na = x509.BasicConstraints(ca=True, path_length=None)
na2 = x509.BasicConstraints(ca=True, path_length=None)
@@ -833,17 +1032,33 @@ class TestExtensions(object):
assert exc.value.oid == x509.ObjectIdentifier("1.2.3.4")
+ @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
def test_unsupported_extension(self, backend):
- # TODO: this will raise an exception when all extensions are complete
cert = _load_cert(
os.path.join(
- "x509", "custom", "unsupported_extension.pem"
+ "x509", "custom", "unsupported_extension_2.pem"
),
x509.load_pem_x509_certificate,
backend
)
extensions = cert.extensions
- assert len(extensions) == 0
+ assert len(extensions) == 2
+ assert extensions[0].critical is False
+ assert extensions[0].oid == x509.ObjectIdentifier(
+ "1.3.6.1.4.1.41482.2"
+ )
+ assert extensions[0].value == x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.3.6.1.4.1.41482.2"),
+ b"1.3.6.1.4.1.41482.1.2"
+ )
+ assert extensions[1].critical is False
+ assert extensions[1].oid == x509.ObjectIdentifier(
+ "1.3.6.1.4.1.45724.2.1.1"
+ )
+ assert extensions[1].value == x509.UnrecognizedExtension(
+ x509.ObjectIdentifier("1.3.6.1.4.1.45724.2.1.1"),
+ b"\x03\x02\x040"
+ )
def test_no_extensions_get_for_class(self, backend):
cert = _load_cert(
@@ -858,6 +1073,21 @@ class TestExtensions(object):
exts.get_extension_for_class(x509.IssuerAlternativeName)
assert exc.value.oid == ExtensionOID.ISSUER_ALTERNATIVE_NAME
+ def test_unrecognized_extension_for_class(self):
+ exts = x509.Extensions([])
+ with pytest.raises(TypeError):
+ exts.get_extension_for_class(x509.UnrecognizedExtension)
+
+ def test_indexing(self, backend):
+ cert = _load_cert(
+ os.path.join("x509", "cryptography.io.pem"),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ exts = cert.extensions
+ assert exts[-1] == exts[7]
+ assert exts[2:6:2] == [exts[2], exts[4]]
+
def test_one_extension_get_for_class(self, backend):
cert = _load_cert(
os.path.join(
@@ -1379,6 +1609,17 @@ class TestGeneralNames(object):
x509.DNSName(u"crypto.local"),
]
+ def test_indexing(self):
+ gn = x509.GeneralNames([
+ x509.DNSName(u"cryptography.io"),
+ x509.DNSName(u"crypto.local"),
+ x509.DNSName(u"another.local"),
+ x509.RFC822Name(u"email@another.local"),
+ x509.UniformResourceIdentifier(u"http://another.local"),
+ ])
+ assert gn[-1] == gn[4]
+ assert gn[2:6:2] == [gn[2], gn[4]]
+
def test_invalid_general_names(self):
with pytest.raises(TypeError):
x509.GeneralNames(
@@ -1434,6 +1675,17 @@ class TestIssuerAlternativeName(object):
x509.DNSName(u"crypto.local"),
]
+ def test_indexing(self):
+ ian = x509.IssuerAlternativeName([
+ x509.DNSName(u"cryptography.io"),
+ x509.DNSName(u"crypto.local"),
+ x509.DNSName(u"another.local"),
+ x509.RFC822Name(u"email@another.local"),
+ x509.UniformResourceIdentifier(u"http://another.local"),
+ ])
+ assert ian[-1] == ian[4]
+ assert ian[2:6:2] == [ian[2], ian[4]]
+
def test_invalid_general_names(self):
with pytest.raises(TypeError):
x509.IssuerAlternativeName(
@@ -1506,6 +1758,13 @@ class TestCRLNumber(object):
with pytest.raises(TypeError):
x509.CRLNumber("notanumber")
+ def test_hash(self):
+ c1 = x509.CRLNumber(1)
+ c2 = x509.CRLNumber(1)
+ c3 = x509.CRLNumber(2)
+ assert hash(c1) == hash(c2)
+ assert hash(c1) != hash(c3)
+
class TestSubjectAlternativeName(object):
def test_get_values_for_type(self):
@@ -1526,6 +1785,17 @@ class TestSubjectAlternativeName(object):
x509.DNSName(u"crypto.local"),
]
+ def test_indexing(self):
+ san = x509.SubjectAlternativeName([
+ x509.DNSName(u"cryptography.io"),
+ x509.DNSName(u"crypto.local"),
+ x509.DNSName(u"another.local"),
+ x509.RFC822Name(u"email@another.local"),
+ x509.UniformResourceIdentifier(u"http://another.local"),
+ ])
+ assert san[-1] == san[4]
+ assert san[2:6:2] == [san[2], san[4]]
+
def test_invalid_general_names(self):
with pytest.raises(TypeError):
x509.SubjectAlternativeName(
@@ -2028,6 +2298,32 @@ class TestAuthorityInformationAccess(object):
assert aia != aia2
assert aia != object()
+ def test_indexing(self):
+ aia = x509.AuthorityInformationAccess([
+ x509.AccessDescription(
+ AuthorityInformationAccessOID.OCSP,
+ x509.UniformResourceIdentifier(u"http://ocsp.domain.com")
+ ),
+ x509.AccessDescription(
+ AuthorityInformationAccessOID.CA_ISSUERS,
+ x509.UniformResourceIdentifier(u"http://domain.com/ca.crt")
+ ),
+ x509.AccessDescription(
+ AuthorityInformationAccessOID.OCSP,
+ x509.UniformResourceIdentifier(u"http://ocsp2.domain.com")
+ ),
+ x509.AccessDescription(
+ AuthorityInformationAccessOID.OCSP,
+ x509.UniformResourceIdentifier(u"http://ocsp3.domain.com")
+ ),
+ x509.AccessDescription(
+ AuthorityInformationAccessOID.OCSP,
+ x509.UniformResourceIdentifier(u"http://ocsp4.domain.com")
+ ),
+ ])
+ assert aia[-1] == aia[4]
+ assert aia[2:6:2] == [aia[2], aia[4]]
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
@@ -2756,6 +3052,32 @@ class TestCRLDistributionPoints(object):
assert cdp != cdp4
assert cdp != object()
+ def test_indexing(self):
+ ci = x509.CRLDistributionPoints([
+ x509.DistributionPoint(
+ None, None, None,
+ [x509.UniformResourceIdentifier(u"uri://thing")],
+ ),
+ x509.DistributionPoint(
+ None, None, None,
+ [x509.UniformResourceIdentifier(u"uri://thing2")],
+ ),
+ x509.DistributionPoint(
+ None, None, None,
+ [x509.UniformResourceIdentifier(u"uri://thing3")],
+ ),
+ x509.DistributionPoint(
+ None, None, None,
+ [x509.UniformResourceIdentifier(u"uri://thing4")],
+ ),
+ x509.DistributionPoint(
+ None, None, None,
+ [x509.UniformResourceIdentifier(u"uri://thing5")],
+ ),
+ ])
+ assert ci[-1] == ci[4]
+ assert ci[2:6:2] == [ci[2], ci[4]]
+
@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=X509Backend)
diff --git a/tests/test_x509_revokedcertbuilder.py b/tests/test_x509_revokedcertbuilder.py
new file mode 100644
index 00000000..5aa90630
--- /dev/null
+++ b/tests/test_x509_revokedcertbuilder.py
@@ -0,0 +1,160 @@
+# 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 datetime
+
+import pytest
+
+from cryptography import x509
+from cryptography.hazmat.backends.interfaces import X509Backend
+
+
+class TestRevokedCertificateBuilder(object):
+ def test_serial_number_must_be_integer(self):
+ with pytest.raises(TypeError):
+ x509.RevokedCertificateBuilder().serial_number("notanx509name")
+
+ def test_serial_number_must_be_non_negative(self):
+ with pytest.raises(ValueError):
+ x509.RevokedCertificateBuilder().serial_number(-1)
+
+ def test_serial_number_must_be_less_than_160_bits_long(self):
+ with pytest.raises(ValueError):
+ # 2 raised to the 160th power is actually 161 bits
+ x509.RevokedCertificateBuilder().serial_number(2 ** 160)
+
+ def test_set_serial_number_twice(self):
+ builder = x509.RevokedCertificateBuilder().serial_number(3)
+ with pytest.raises(ValueError):
+ builder.serial_number(4)
+
+ def test_revocation_date_invalid(self):
+ with pytest.raises(TypeError):
+ x509.RevokedCertificateBuilder().revocation_date("notadatetime")
+
+ def test_revocation_date_before_unix_epoch(self):
+ with pytest.raises(ValueError):
+ x509.RevokedCertificateBuilder().revocation_date(
+ datetime.datetime(1960, 8, 10)
+ )
+
+ def test_set_revocation_date_twice(self):
+ builder = x509.RevokedCertificateBuilder().revocation_date(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+ with pytest.raises(ValueError):
+ builder.revocation_date(datetime.datetime(2002, 1, 1, 12, 1))
+
+ def test_add_extension_checks_for_duplicates(self):
+ builder = x509.RevokedCertificateBuilder().add_extension(
+ x509.CRLReason(x509.ReasonFlags.ca_compromise), False
+ )
+
+ with pytest.raises(ValueError):
+ builder.add_extension(
+ x509.CRLReason(x509.ReasonFlags.ca_compromise), False
+ )
+
+ def test_add_invalid_extension(self):
+ with pytest.raises(TypeError):
+ x509.RevokedCertificateBuilder().add_extension(
+ "notanextension", False
+ )
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_serial_number(self, backend):
+ builder = x509.RevokedCertificateBuilder().revocation_date(
+ datetime.datetime(2002, 1, 1, 12, 1)
+ )
+
+ with pytest.raises(ValueError):
+ builder.build(backend)
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_no_revocation_date(self, backend):
+ builder = x509.RevokedCertificateBuilder().serial_number(3)
+
+ with pytest.raises(ValueError):
+ builder.build(backend)
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_create_revoked(self, backend):
+ serial_number = 333
+ revocation_date = datetime.datetime(2002, 1, 1, 12, 1)
+ builder = x509.RevokedCertificateBuilder().serial_number(
+ serial_number
+ ).revocation_date(
+ revocation_date
+ )
+
+ revoked_certificate = builder.build(backend)
+ assert revoked_certificate.serial_number == serial_number
+ assert revoked_certificate.revocation_date == revocation_date
+ assert len(revoked_certificate.extensions) == 0
+
+ @pytest.mark.parametrize(
+ "extension",
+ [
+ x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)),
+ x509.CRLReason(x509.ReasonFlags.ca_compromise),
+ x509.CertificateIssuer([
+ x509.DNSName(u"cryptography.io"),
+ ])
+ ]
+ )
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_add_extensions(self, backend, extension):
+ serial_number = 333
+ revocation_date = datetime.datetime(2002, 1, 1, 12, 1)
+ builder = x509.RevokedCertificateBuilder().serial_number(
+ serial_number
+ ).revocation_date(
+ revocation_date
+ ).add_extension(
+ extension, False
+ )
+
+ revoked_certificate = builder.build(backend)
+ assert revoked_certificate.serial_number == serial_number
+ assert revoked_certificate.revocation_date == revocation_date
+ assert len(revoked_certificate.extensions) == 1
+ ext = revoked_certificate.extensions.get_extension_for_class(
+ type(extension)
+ )
+ assert ext.critical is False
+ assert ext.value == extension
+
+ @pytest.mark.requires_backend_interface(interface=X509Backend)
+ def test_add_multiple_extensions(self, backend):
+ serial_number = 333
+ revocation_date = datetime.datetime(2002, 1, 1, 12, 1)
+ invalidity_date = x509.InvalidityDate(
+ datetime.datetime(2015, 1, 1, 0, 0)
+ )
+ certificate_issuer = x509.CertificateIssuer([
+ x509.DNSName(u"cryptography.io"),
+ ])
+ crl_reason = x509.CRLReason(x509.ReasonFlags.aa_compromise)
+ builder = x509.RevokedCertificateBuilder().serial_number(
+ serial_number
+ ).revocation_date(
+ revocation_date
+ ).add_extension(
+ invalidity_date, True
+ ).add_extension(
+ crl_reason, True
+ ).add_extension(
+ certificate_issuer, True
+ )
+
+ revoked_certificate = builder.build(backend)
+ assert len(revoked_certificate.extensions) == 3
+ for ext_data in [invalidity_date, certificate_issuer, crl_reason]:
+ ext = revoked_certificate.extensions.get_extension_for_class(
+ type(ext_data)
+ )
+ assert ext.critical is True
+ assert ext.value == ext_data
diff --git a/vectors/cryptography_vectors/__about__.py b/vectors/cryptography_vectors/__about__.py
index 63f0b572..11bd69fb 100644
--- a/vectors/cryptography_vectors/__about__.py
+++ b/vectors/cryptography_vectors/__about__.py
@@ -20,4 +20,4 @@ __author__ = "The cryptography developers"
__email__ = "cryptography-dev@python.org"
__license__ = "BSD or Apache License, Version 2.0"
-__copyright__ = "Copyright 2013-2015 %s" % __author__
+__copyright__ = "Copyright 2013-2016 %s" % __author__
diff --git a/vectors/cryptography_vectors/x509/custom/unsupported_extension_2.pem b/vectors/cryptography_vectors/x509/custom/unsupported_extension_2.pem
new file mode 100644
index 00000000..4b7d2565
--- /dev/null
+++ b/vectors/cryptography_vectors/x509/custom/unsupported_extension_2.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICIjCCAQygAwIBAgIEIHHwozALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEdGVz
+dDAeFw0xNTA4MTEwOTAwMzNaFw0xNjA4MTAwOTAwMzNaMCkxJzAlBgNVBAMTHll1
+YmljbyBVMkYgRUUgU2VyaWFsIDU0NDMzODA4MzBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABPdFG1pBjBBQVhLrD39Qg1vKjuR2kRdBZnwLI/zgzztQpf4ffpkrkB/3
+E0TXj5zg8gN9sgMkX48geBe+tBEpvMmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYu
+MS40LjEuNDE0ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEB
+CwOCAQEAb3YpnmHHduNuWEXlLqlnww9034ZeZaojhPAYSLR8d5NPk9gc0hkjQKmI
+aaBM7DsaHbcHMKpXoMGTQSC++NCZTcKvZ0Lt12mp5HRnM1NNBPol8Hte5fLmvW4t
+Q9EzLl4gkz7LSlORxTuwTbae1eQqNdxdeB+0ilMFCEUc+3NGCNM0RWd+sP5+gzMX
+BDQAI1Sc9XaPIg8t3du5JChAl1ifpu/uERZ2WQgtxeBDO6z1Xoa5qz4svf5oURjP
+ZjxS0WUKht48Z2rIjk5lZzERSaY3RrX3UtrnZEIzCmInXOrcRPeAD4ZutpiwuHe6
+2ABsjuMRnKbATbOUiLdknNyPYYQz2g==
+-----END CERTIFICATE-----