diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rwxr-xr-x | .travis/install.sh | 7 | ||||
-rw-r--r-- | CHANGELOG.rst | 4 | ||||
-rw-r--r-- | README.rst | 2 | ||||
-rw-r--r-- | docs/development/submitting-patches.rst | 2 | ||||
-rw-r--r-- | docs/development/test-vectors.rst | 4 | ||||
-rw-r--r-- | docs/installation.rst | 4 | ||||
-rw-r--r-- | docs/spelling_wordlist.txt | 1 | ||||
-rw-r--r-- | docs/x509.rst | 85 | ||||
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 29 | ||||
-rw-r--r-- | src/cryptography/hazmat/bindings/openssl/x509.py | 1 | ||||
-rw-r--r-- | src/cryptography/x509.py | 51 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 10 | ||||
-rw-r--r-- | tests/test_x509.py | 14 | ||||
-rw-r--r-- | vectors/cryptography_vectors/x509/verisign_md2_root.pem | 14 |
16 files changed, 214 insertions, 19 deletions
diff --git a/.travis.yml b/.travis.yml index 9b900eb9..b6f6f0cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,8 @@ notifications: - "irc.freenode.org#cryptography-dev" use_notice: true skip_join: true + webhooks: + - https://buildtimetrend.herokuapp.com/travis matrix: exclude: diff --git a/.travis/install.sh b/.travis/install.sh index 26a82c44..f82cc4a3 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -3,13 +3,8 @@ set -e set -x -if [[ "$(uname -s)" == 'Darwin' ]]; then - DARWIN=true -else - DARWIN=false -fi -if [[ "$DARWIN" = true ]]; then +if [[ "$(uname -s)" == 'Darwin' ]]; then brew update if [[ "${OPENSSL}" != "0.9.8" ]]; then diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e106ff44..da529f68 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ Changelog * :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` can now load elliptic curve public keys. * Added + :attr:`~cryptography.x509.Certificate.signature_hash_algorithm` support to + :class:`~cryptography.x509.Certificate`. +* Added :func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_prime_factors` * :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` was moved from :mod:`~cryptography.hazmat.primitives.interfaces` to @@ -74,6 +77,7 @@ Changelog :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` to support loading DER encoded public keys. * Fixed building against LibreSSL, a compile-time substitute for OpenSSL. +* FreeBSD 9.2 was removed from the continuous integration system. 0.7.2 - 2015-01-16 ~~~~~~~~~~~~~~~~~~ @@ -1,7 +1,7 @@ Cryptography ============ -.. image:: https://pypip.in/version/cryptography/badge.svg +.. image:: https://pypip.in/version/cryptography/badge.svg?style=flat :target: https://pypi.python.org/pypi/cryptography/ :alt: Latest Version diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst index 810b8748..66105843 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`: http://www.ibm.com/developerworks/rational/library/11-proven-practices-for-peer-review/ +.. _`Studies have shown`: https://smartbear.com/smartbear/media/pdfs/wp-cc-11-best-practices-of-peer-code-review.pdf .. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev .. _`doc8`: https://github.com/stackforge/doc8 diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 4c048abf..2cd9faa6 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -80,6 +80,9 @@ X.509 * ``v1_cert.pem`` from the OpenSSL source tree (`testx509.pem`_). * ``ecdsa_root.pem`` - `DigiCert Global Root G3`_, a ``secp384r1`` ECDSA root certificate. +* ``verisign-md2-root.pem`` - A legacy Verisign public root signed using the + MD2 algorithm. This is a PEM conversion of the `root data`_ in the NSS source + tree. Custom X.509 Vectors ~~~~~~~~~~~~~~~~~~~~ @@ -219,3 +222,4 @@ header format (substituting the correct information): .. _`NIST PKI Testing`: http://csrc.nist.gov/groups/ST/crypto_apps_infra/pki/pkitesting.html .. _`testx509.pem`: https://github.com/openssl/openssl/blob/master/test/testx509.pem .. _`DigiCert Global Root G3`: http://cacerts.digicert.com/DigiCertGlobalRootG3.crt +.. _`root data`: https://hg.mozilla.org/projects/nss/file/25b2922cc564/security/nss/lib/ckfw/builtins/certdata.txt#l2053 diff --git a/docs/installation.rst b/docs/installation.rst index c8a37bfa..c061903e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -14,7 +14,7 @@ Currently we test ``cryptography`` on Python 2.6, 2.7, 3.2, 3.3, 3.4 and PyPy on these operating systems. * x86-64 CentOS 7.x, 6.4 and CentOS 5.x -* x86-64 FreeBSD 9.2 and FreeBSD 10 +* x86-64 FreeBSD 10 * OS X 10.10 Yosemite, 10.9 Mavericks, 10.8 Mountain Lion, and 10.7 Lion * x86-64 Ubuntu 12.04 LTS * x86-64 Debian Wheezy (7.x) and Jessie (8.x) @@ -30,7 +30,7 @@ OpenSSL releases: * ``OpenSSL 1.0.0-fips`` (``RHEL/CentOS 6.4``) * ``OpenSSL 1.0.1`` * ``OpenSSL 1.0.1e-fips`` (``RHEL/CentOS 7``) -* ``OpenSSL 1.0.1e-freebsd`` +* ``OpenSSL 1.0.1j-freebsd`` * ``OpenSSL 1.0.1-latest`` (The most recent 1.0.1 release) * ``OpenSSL 1.0.2`` diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index fefd26b3..ddd37897 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -51,3 +51,4 @@ Ubuntu unencrypted unpadded unpadding +Verisign diff --git a/docs/x509.rst b/docs/x509.rst index 0298d94d..27f1d544 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -182,6 +182,19 @@ X.509 Certificate Object The :class:`Name` of the subject. + .. attribute:: signature_hash_algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + Returns the + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which + was used in signing this certificate. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> isinstance(cert.signature_hash_algorithm, hashes.SHA256) + True .. class:: Name @@ -266,6 +279,9 @@ Object Identifiers X.509 elements are frequently identified by :class:`ObjectIdentifier` instances. The following common OIDs are available as constants. +Name OIDs +~~~~~~~~~ + .. data:: OID_COMMON_NAME Corresponds to the dotted string ``"2.5.4.3"``. Historically the domain @@ -346,6 +362,75 @@ instances. The following common OIDs are available as constants. Corresponds to the dotted string ``"1.2.840.113549.1.9.1"``. This OID is typically seen in X.509 names. +Signature Algorithm OIDs +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. data:: OID_RSA_WITH_MD5 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.4"``. This is + an MD5 digest signed by an RSA key. + +.. data:: OID_RSA_WITH_SHA1 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.5"``. This is + a SHA1 digest signed by an RSA key. + +.. data:: OID_RSA_WITH_SHA224 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.14"``. This is + a SHA224 digest signed by an RSA key. + +.. data:: OID_RSA_WITH_SHA256 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.11"``. This is + a SHA256 digest signed by an RSA key. + +.. data:: OID_RSA_WITH_SHA384 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.12"``. This is + a SHA384 digest signed by an RSA key. + +.. data:: OID_RSA_WITH_SHA512 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.13"``. This is + a SHA512 digest signed by an RSA key. + +.. data:: OID_ECDSA_WITH_SHA224 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.1"``. This is + a SHA224 digest signed by an ECDSA key. + +.. data:: OID_ECDSA_WITH_SHA256 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.2"``. This is + a SHA256 digest signed by an ECDSA key. + +.. data:: OID_ECDSA_WITH_SHA384 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.3"``. This is + a SHA384 digest signed by an ECDSA key. + +.. data:: OID_ECDSA_WITH_SHA512 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.4"``. This is + a SHA512 digest signed by an ECDSA key. + +.. data:: OID_DSA_WITH_SHA1 + + Corresponds to the dotted string ``"1.2.840.10040.4.3"``. This is + a SHA1 digest signed by a DSA key. + +.. data:: OID_DSA_WITH_SHA224 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.1"``. This is + a SHA224 digest signed by a DSA key. + +.. data:: OID_DSA_WITH_SHA256 + + Corresponds to the dotted string ``2.16.840.1.101.3.4.3.2"``. This is + a SHA256 digest signed by a DSA key. + + Exceptions ~~~~~~~~~~ @@ -135,7 +135,8 @@ class PyTest(test): def run_tests(self): # Import here because in module scope the eggs are not loaded. import pytest - errno = pytest.main(self.test_args) + test_args = [os.path.join(base_dir, "tests")] + errno = pytest.main(test_args) sys.exit(errno) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 76dcf32f..b712f1f9 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -16,6 +16,7 @@ from __future__ import absolute_import, division, print_function import datetime from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives import hashes @@ -121,14 +122,7 @@ class _Certificate(object): buf, lambda buf: self._backend._lib.OPENSSL_free(buf[0]) ) value = self._backend._ffi.buffer(buf[0], res)[:].decode('utf8') - # Set to 80 on the recommendation of - # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html - buf_len = 80 - buf = self._backend._ffi.new("char[]", buf_len) - res = self._backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1) - assert res > 0 - oid = self._backend._ffi.buffer(buf, res)[:].decode() - + oid = self._obj2txt(obj) attributes.append( x509.NameAttribute( x509.ObjectIdentifier(oid), value @@ -136,3 +130,22 @@ class _Certificate(object): ) return x509.Name(attributes) + + def _obj2txt(self, obj): + # Set to 80 on the recommendation of + # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values + buf_len = 80 + buf = self._backend._ffi.new("char[]", buf_len) + res = self._backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1) + assert res > 0 + return self._backend._ffi.buffer(buf, res)[:].decode() + + @property + def signature_hash_algorithm(self): + oid = self._obj2txt(self._x509.sig_alg.algorithm) + try: + return x509._SIG_OIDS_TO_HASH[oid] + except KeyError: + raise UnsupportedAlgorithm( + "Signature algorithm OID:{0} not recognized".format(oid) + ) diff --git a/src/cryptography/hazmat/bindings/openssl/x509.py b/src/cryptography/hazmat/bindings/openssl/x509.py index 585a2e90..f5638da7 100644 --- a/src/cryptography/hazmat/bindings/openssl/x509.py +++ b/src/cryptography/hazmat/bindings/openssl/x509.py @@ -65,6 +65,7 @@ typedef struct { } X509_CRL; typedef struct { + X509_ALGOR *sig_alg; X509_CINF *cert_info; ...; } X509; diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 8a6ecc8d..ad7ebbe0 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -10,6 +10,7 @@ from enum import Enum import six from cryptography import utils +from cryptography.hazmat.primitives import hashes _OID_NAMES = { @@ -28,6 +29,19 @@ _OID_NAMES = { "2.5.4.65": "pseudonym", "0.9.2342.19200300.100.1.25": "domainComponent", "1.2.840.113549.1.9.1": "emailAddress", + "1.2.840.113549.1.1.4": "md5WithRSAEncryption", + "1.2.840.113549.1.1.5": "sha1WithRSAEncryption", + "1.2.840.113549.1.1.14": "sha224WithRSAEncryption", + "1.2.840.113549.1.1.11": "sha256WithRSAEncryption", + "1.2.840.113549.1.1.12": "sha384WithRSAEncryption", + "1.2.840.113549.1.1.13": "sha512WithRSAEncryption", + "1.2.840.10045.4.3.1": "ecdsa-with-SHA224", + "1.2.840.10045.4.3.2": "ecdsa-with-SHA256", + "1.2.840.10045.4.3.3": "ecdsa-with-SHA384", + "1.2.840.10045.4.3.4": "ecdsa-with-SHA512", + "1.2.840.10040.4.3": "dsa-with-sha1", + "2.16.840.1.101.3.4.3.1": "dsa-with-sha224", + "2.16.840.1.101.3.4.3.2": "dsa-with-sha256", } @@ -143,6 +157,36 @@ OID_PSEUDONYM = ObjectIdentifier("2.5.4.65") OID_DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") OID_EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") +OID_RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") +OID_RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") +OID_RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") +OID_RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") +OID_RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") +OID_RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") +OID_ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") +OID_ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") +OID_ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") +OID_ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") +OID_DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") +OID_DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") +OID_DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") + +_SIG_OIDS_TO_HASH = { + OID_RSA_WITH_MD5.dotted_string: hashes.MD5(), + OID_RSA_WITH_SHA1.dotted_string: hashes.SHA1(), + OID_RSA_WITH_SHA224.dotted_string: hashes.SHA224(), + OID_RSA_WITH_SHA256.dotted_string: hashes.SHA256(), + OID_RSA_WITH_SHA384.dotted_string: hashes.SHA384(), + OID_RSA_WITH_SHA512.dotted_string: hashes.SHA512(), + OID_ECDSA_WITH_SHA224.dotted_string: hashes.SHA224(), + OID_ECDSA_WITH_SHA256.dotted_string: hashes.SHA256(), + OID_ECDSA_WITH_SHA384.dotted_string: hashes.SHA384(), + OID_ECDSA_WITH_SHA512.dotted_string: hashes.SHA512(), + OID_DSA_WITH_SHA1.dotted_string: hashes.SHA1(), + OID_DSA_WITH_SHA224.dotted_string: hashes.SHA224(), + OID_DSA_WITH_SHA256.dotted_string: hashes.SHA256() +} + @six.add_metaclass(abc.ABCMeta) class Certificate(object): @@ -193,3 +237,10 @@ class Certificate(object): """ Returns the subject name object. """ + + @abc.abstractproperty + def signature_hash_algorithm(self): + """ + Returns a HashAlgorithm corresponding to the type of the digest signed + in the certificate. + """ diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 2bf66a0c..0e4d75ed 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -234,9 +234,19 @@ class TestOpenSSLRandomEngine(object): ) engine_name = tmpdir.join('engine_name') + # If we're running tests via ``python setup.py test`` in a clean + # environment then all of our dependencies are going to be installed + # into either the current directory or the .eggs directory. However the + # subprocess won't know to activate these dependencies, so we'll get it + # to do so by passing our entire sys.path into the subprocess via the + # PYTHONPATH environment variable. + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join(sys.path) + with engine_name.open('w') as out: subprocess.check_call( [sys.executable, "-c", engine_printer], + env=env, stdout=out ) diff --git a/tests/test_x509.py b/tests/test_x509.py index 55a94084..8f00eeed 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -13,6 +13,7 @@ import pytest import six from cryptography import x509 +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.interfaces import ( DSABackend, EllipticCurveBackend, RSABackend, X509Backend ) @@ -45,6 +46,7 @@ class TestRSACertificate(object): assert cert.serial == 11559813051657483483 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8" + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) def test_load_der_cert(self, backend): cert = _load_cert( @@ -56,6 +58,7 @@ class TestRSACertificate(object): assert cert.serial == 2 fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) def test_issuer(self, backend): cert = _load_cert( @@ -328,6 +331,15 @@ class TestRSACertificate(object): with pytest.raises(ValueError): x509.load_der_x509_certificate(b"notacert", backend) + def test_unsupported_signature_hash_algorithm_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(UnsupportedAlgorithm): + cert.signature_hash_algorithm + @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -338,6 +350,7 @@ class TestDSACertificate(object): x509.load_pem_x509_certificate, backend ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) public_key = cert.public_key() assert isinstance(public_key, interfaces.DSAPublicKey) if isinstance(public_key, interfaces.DSAPublicKeyWithNumbers): @@ -390,6 +403,7 @@ class TestECDSACertificate(object): x509.load_pem_x509_certificate, backend ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) public_key = cert.public_key() assert isinstance(public_key, interfaces.EllipticCurvePublicKey) if isinstance( diff --git a/vectors/cryptography_vectors/x509/verisign_md2_root.pem b/vectors/cryptography_vectors/x509/verisign_md2_root.pem new file mode 100644 index 00000000..87676acf --- /dev/null +++ b/vectors/cryptography_vectors/x509/verisign_md2_root.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- |