Merge pull request #1179 from reaperhulk/dsa-numbers-opaque-backend
DSA Opaque Keys for OpenSSL
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 8d167ab..b3396ea 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -31,6 +31,7 @@
from cryptography.hazmat.backends.openssl.ciphers import (
_AESCTRCipherContext, _CipherContext
)
+from cryptography.hazmat.backends.openssl.cmac import _CMACContext
from cryptography.hazmat.backends.openssl.dsa import (
_DSAParameters, _DSAPrivateKey, _DSAPublicKey,
_DSASignatureContext, _DSAVerificationContext
@@ -39,12 +40,14 @@
_ECDSASignatureContext, _ECDSAVerificationContext,
_EllipticCurvePrivateKey, _EllipticCurvePublicKey
)
+from cryptography.hazmat.backends.openssl.hashes import _HashContext
+from cryptography.hazmat.backends.openssl.hmac import _HMACContext
from cryptography.hazmat.backends.openssl.rsa import (
_RSAPrivateKey, _RSAPublicKey, _RSASignatureContext,
_RSAVerificationContext
)
from cryptography.hazmat.bindings.openssl.binding import Binding
-from cryptography.hazmat.primitives import hashes, interfaces
+from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
from cryptography.hazmat.primitives.asymmetric.padding import (
MGF1, OAEP, PKCS1v15, PSS
@@ -1026,19 +1029,25 @@
Generate a new private key on the named curve.
"""
- curve_nid = self._elliptic_curve_to_nid(curve)
+ if backend.elliptic_curve_supported(curve):
+ curve_nid = self._elliptic_curve_to_nid(curve)
- ctx = self._lib.EC_KEY_new_by_curve_name(curve_nid)
- assert ctx != self._ffi.NULL
- ctx = self._ffi.gc(ctx, self._lib.EC_KEY_free)
+ ctx = self._lib.EC_KEY_new_by_curve_name(curve_nid)
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.EC_KEY_free)
- res = self._lib.EC_KEY_generate_key(ctx)
- assert res == 1
+ res = self._lib.EC_KEY_generate_key(ctx)
+ assert res == 1
- res = self._lib.EC_KEY_check_key(ctx)
- assert res == 1
+ res = self._lib.EC_KEY_check_key(ctx)
+ assert res == 1
- return _EllipticCurvePrivateKey(self, ctx, curve)
+ return _EllipticCurvePrivateKey(self, ctx, curve)
+ else:
+ raise UnsupportedAlgorithm(
+ "Backend object does not support {0}.".format(curve.name),
+ _Reasons.UNSUPPORTED_ELLIPTIC_CURVE
+ )
def elliptic_curve_private_key_from_numbers(self, numbers):
ec_key = self._ec_key_cdata_from_private_numbers(numbers)
@@ -1204,175 +1213,4 @@
return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii"))
-@utils.register_interface(interfaces.HashContext)
-class _HashContext(object):
- def __init__(self, backend, algorithm, ctx=None):
- self.algorithm = algorithm
-
- self._backend = backend
-
- if ctx is None:
- ctx = self._backend._lib.EVP_MD_CTX_create()
- ctx = self._backend._ffi.gc(ctx,
- self._backend._lib.EVP_MD_CTX_destroy)
- evp_md = self._backend._lib.EVP_get_digestbyname(
- algorithm.name.encode("ascii"))
- if evp_md == self._backend._ffi.NULL:
- raise UnsupportedAlgorithm(
- "{0} is not a supported hash on this backend.".format(
- algorithm.name),
- _Reasons.UNSUPPORTED_HASH
- )
- res = self._backend._lib.EVP_DigestInit_ex(ctx, evp_md,
- self._backend._ffi.NULL)
- assert res != 0
-
- self._ctx = ctx
-
- def copy(self):
- copied_ctx = self._backend._lib.EVP_MD_CTX_create()
- copied_ctx = self._backend._ffi.gc(
- copied_ctx, self._backend._lib.EVP_MD_CTX_destroy
- )
- res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx)
- assert res != 0
- return _HashContext(self._backend, self.algorithm, ctx=copied_ctx)
-
- def update(self, data):
- res = self._backend._lib.EVP_DigestUpdate(self._ctx, data, len(data))
- assert res != 0
-
- def finalize(self):
- buf = self._backend._ffi.new("unsigned char[]",
- self._backend._lib.EVP_MAX_MD_SIZE)
- outlen = self._backend._ffi.new("unsigned int *")
- res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen)
- assert res != 0
- assert outlen[0] == self.algorithm.digest_size
- res = self._backend._lib.EVP_MD_CTX_cleanup(self._ctx)
- assert res == 1
- return self._backend._ffi.buffer(buf)[:outlen[0]]
-
-
-@utils.register_interface(interfaces.HashContext)
-class _HMACContext(object):
- def __init__(self, backend, key, algorithm, ctx=None):
- self.algorithm = algorithm
- self._backend = backend
-
- if ctx is None:
- ctx = self._backend._ffi.new("HMAC_CTX *")
- self._backend._lib.HMAC_CTX_init(ctx)
- ctx = self._backend._ffi.gc(
- ctx, self._backend._lib.HMAC_CTX_cleanup
- )
- evp_md = self._backend._lib.EVP_get_digestbyname(
- algorithm.name.encode('ascii'))
- if evp_md == self._backend._ffi.NULL:
- raise UnsupportedAlgorithm(
- "{0} is not a supported hash on this backend.".format(
- algorithm.name),
- _Reasons.UNSUPPORTED_HASH
- )
- res = self._backend._lib.Cryptography_HMAC_Init_ex(
- ctx, key, len(key), evp_md, self._backend._ffi.NULL
- )
- assert res != 0
-
- self._ctx = ctx
- self._key = key
-
- def copy(self):
- copied_ctx = self._backend._ffi.new("HMAC_CTX *")
- self._backend._lib.HMAC_CTX_init(copied_ctx)
- copied_ctx = self._backend._ffi.gc(
- copied_ctx, self._backend._lib.HMAC_CTX_cleanup
- )
- res = self._backend._lib.Cryptography_HMAC_CTX_copy(
- copied_ctx, self._ctx
- )
- assert res != 0
- return _HMACContext(
- self._backend, self._key, self.algorithm, ctx=copied_ctx
- )
-
- def update(self, data):
- res = self._backend._lib.Cryptography_HMAC_Update(
- self._ctx, data, len(data)
- )
- assert res != 0
-
- def finalize(self):
- buf = self._backend._ffi.new("unsigned char[]",
- self._backend._lib.EVP_MAX_MD_SIZE)
- outlen = self._backend._ffi.new("unsigned int *")
- res = self._backend._lib.Cryptography_HMAC_Final(
- self._ctx, buf, outlen
- )
- assert res != 0
- assert outlen[0] == self.algorithm.digest_size
- self._backend._lib.HMAC_CTX_cleanup(self._ctx)
- return self._backend._ffi.buffer(buf)[:outlen[0]]
-
-
-@utils.register_interface(interfaces.CMACContext)
-class _CMACContext(object):
- def __init__(self, backend, algorithm, ctx=None):
- if not backend.cmac_algorithm_supported(algorithm):
- raise UnsupportedAlgorithm("This backend does not support CMAC.",
- _Reasons.UNSUPPORTED_CIPHER)
-
- self._backend = backend
- self._key = algorithm.key
- self._algorithm = algorithm
- self._output_length = algorithm.block_size // 8
-
- if ctx is None:
- registry = self._backend._cipher_registry
- adapter = registry[type(algorithm), CBC]
-
- evp_cipher = adapter(self._backend, algorithm, CBC)
-
- ctx = self._backend._lib.CMAC_CTX_new()
-
- assert ctx != self._backend._ffi.NULL
- ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free)
-
- self._backend._lib.CMAC_Init(
- ctx, self._key, len(self._key),
- evp_cipher, self._backend._ffi.NULL
- )
-
- self._ctx = ctx
-
- def update(self, data):
- res = self._backend._lib.CMAC_Update(self._ctx, data, len(data))
- assert res == 1
-
- def finalize(self):
- buf = self._backend._ffi.new("unsigned char[]", self._output_length)
- length = self._backend._ffi.new("size_t *", self._output_length)
- res = self._backend._lib.CMAC_Final(
- self._ctx, buf, length
- )
- assert res == 1
-
- self._ctx = None
-
- return self._backend._ffi.buffer(buf)[:]
-
- def copy(self):
- copied_ctx = self._backend._lib.CMAC_CTX_new()
- copied_ctx = self._backend._ffi.gc(
- copied_ctx, self._backend._lib.CMAC_CTX_free
- )
- res = self._backend._lib.CMAC_CTX_copy(
- copied_ctx, self._ctx
- )
- assert res == 1
- return _CMACContext(
- self._backend, self._algorithm, ctx=copied_ctx
- )
-
-
backend = Backend()
diff --git a/cryptography/hazmat/backends/openssl/cmac.py b/cryptography/hazmat/backends/openssl/cmac.py
new file mode 100644
index 0000000..7acf439
--- /dev/null
+++ b/cryptography/hazmat/backends/openssl/cmac.py
@@ -0,0 +1,80 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, division, print_function
+
+
+from cryptography import utils
+from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
+from cryptography.hazmat.primitives import interfaces
+from cryptography.hazmat.primitives.ciphers.modes import CBC
+
+
+@utils.register_interface(interfaces.CMACContext)
+class _CMACContext(object):
+ def __init__(self, backend, algorithm, ctx=None):
+ if not backend.cmac_algorithm_supported(algorithm):
+ raise UnsupportedAlgorithm("This backend does not support CMAC.",
+ _Reasons.UNSUPPORTED_CIPHER)
+
+ self._backend = backend
+ self._key = algorithm.key
+ self._algorithm = algorithm
+ self._output_length = algorithm.block_size // 8
+
+ if ctx is None:
+ registry = self._backend._cipher_registry
+ adapter = registry[type(algorithm), CBC]
+
+ evp_cipher = adapter(self._backend, algorithm, CBC)
+
+ ctx = self._backend._lib.CMAC_CTX_new()
+
+ assert ctx != self._backend._ffi.NULL
+ ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free)
+
+ self._backend._lib.CMAC_Init(
+ ctx, self._key, len(self._key),
+ evp_cipher, self._backend._ffi.NULL
+ )
+
+ self._ctx = ctx
+
+ def update(self, data):
+ res = self._backend._lib.CMAC_Update(self._ctx, data, len(data))
+ assert res == 1
+
+ def finalize(self):
+ buf = self._backend._ffi.new("unsigned char[]", self._output_length)
+ length = self._backend._ffi.new("size_t *", self._output_length)
+ res = self._backend._lib.CMAC_Final(
+ self._ctx, buf, length
+ )
+ assert res == 1
+
+ self._ctx = None
+
+ return self._backend._ffi.buffer(buf)[:]
+
+ def copy(self):
+ copied_ctx = self._backend._lib.CMAC_CTX_new()
+ copied_ctx = self._backend._ffi.gc(
+ copied_ctx, self._backend._lib.CMAC_CTX_free
+ )
+ res = self._backend._lib.CMAC_CTX_copy(
+ copied_ctx, self._ctx
+ )
+ assert res == 1
+ return _CMACContext(
+ self._backend, self._algorithm, ctx=copied_ctx
+ )
diff --git a/cryptography/hazmat/backends/openssl/hashes.py b/cryptography/hazmat/backends/openssl/hashes.py
new file mode 100644
index 0000000..da91eef
--- /dev/null
+++ b/cryptography/hazmat/backends/openssl/hashes.py
@@ -0,0 +1,69 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, division, print_function
+
+
+from cryptography import utils
+from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
+from cryptography.hazmat.primitives import interfaces
+
+
+@utils.register_interface(interfaces.HashContext)
+class _HashContext(object):
+ def __init__(self, backend, algorithm, ctx=None):
+ self.algorithm = algorithm
+
+ self._backend = backend
+
+ if ctx is None:
+ ctx = self._backend._lib.EVP_MD_CTX_create()
+ ctx = self._backend._ffi.gc(ctx,
+ self._backend._lib.EVP_MD_CTX_destroy)
+ evp_md = self._backend._lib.EVP_get_digestbyname(
+ algorithm.name.encode("ascii"))
+ if evp_md == self._backend._ffi.NULL:
+ raise UnsupportedAlgorithm(
+ "{0} is not a supported hash on this backend.".format(
+ algorithm.name),
+ _Reasons.UNSUPPORTED_HASH
+ )
+ res = self._backend._lib.EVP_DigestInit_ex(ctx, evp_md,
+ self._backend._ffi.NULL)
+ assert res != 0
+
+ self._ctx = ctx
+
+ def copy(self):
+ copied_ctx = self._backend._lib.EVP_MD_CTX_create()
+ copied_ctx = self._backend._ffi.gc(
+ copied_ctx, self._backend._lib.EVP_MD_CTX_destroy
+ )
+ res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx)
+ assert res != 0
+ return _HashContext(self._backend, self.algorithm, ctx=copied_ctx)
+
+ def update(self, data):
+ res = self._backend._lib.EVP_DigestUpdate(self._ctx, data, len(data))
+ assert res != 0
+
+ def finalize(self):
+ buf = self._backend._ffi.new("unsigned char[]",
+ self._backend._lib.EVP_MAX_MD_SIZE)
+ outlen = self._backend._ffi.new("unsigned int *")
+ res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen)
+ assert res != 0
+ assert outlen[0] == self.algorithm.digest_size
+ res = self._backend._lib.EVP_MD_CTX_cleanup(self._ctx)
+ assert res == 1
+ return self._backend._ffi.buffer(buf)[:outlen[0]]
diff --git a/cryptography/hazmat/backends/openssl/hmac.py b/cryptography/hazmat/backends/openssl/hmac.py
new file mode 100644
index 0000000..3f1576f
--- /dev/null
+++ b/cryptography/hazmat/backends/openssl/hmac.py
@@ -0,0 +1,80 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, division, print_function
+
+
+from cryptography import utils
+from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
+from cryptography.hazmat.primitives import interfaces
+
+
+@utils.register_interface(interfaces.HashContext)
+class _HMACContext(object):
+ def __init__(self, backend, key, algorithm, ctx=None):
+ self.algorithm = algorithm
+ self._backend = backend
+
+ if ctx is None:
+ ctx = self._backend._ffi.new("HMAC_CTX *")
+ self._backend._lib.HMAC_CTX_init(ctx)
+ ctx = self._backend._ffi.gc(
+ ctx, self._backend._lib.HMAC_CTX_cleanup
+ )
+ evp_md = self._backend._lib.EVP_get_digestbyname(
+ algorithm.name.encode('ascii'))
+ if evp_md == self._backend._ffi.NULL:
+ raise UnsupportedAlgorithm(
+ "{0} is not a supported hash on this backend.".format(
+ algorithm.name),
+ _Reasons.UNSUPPORTED_HASH
+ )
+ res = self._backend._lib.Cryptography_HMAC_Init_ex(
+ ctx, key, len(key), evp_md, self._backend._ffi.NULL
+ )
+ assert res != 0
+
+ self._ctx = ctx
+ self._key = key
+
+ def copy(self):
+ copied_ctx = self._backend._ffi.new("HMAC_CTX *")
+ self._backend._lib.HMAC_CTX_init(copied_ctx)
+ copied_ctx = self._backend._ffi.gc(
+ copied_ctx, self._backend._lib.HMAC_CTX_cleanup
+ )
+ res = self._backend._lib.Cryptography_HMAC_CTX_copy(
+ copied_ctx, self._ctx
+ )
+ assert res != 0
+ return _HMACContext(
+ self._backend, self._key, self.algorithm, ctx=copied_ctx
+ )
+
+ def update(self, data):
+ res = self._backend._lib.Cryptography_HMAC_Update(
+ self._ctx, data, len(data)
+ )
+ assert res != 0
+
+ def finalize(self):
+ buf = self._backend._ffi.new("unsigned char[]",
+ self._backend._lib.EVP_MAX_MD_SIZE)
+ outlen = self._backend._ffi.new("unsigned int *")
+ res = self._backend._lib.Cryptography_HMAC_Final(
+ self._ctx, buf, outlen
+ )
+ assert res != 0
+ assert outlen[0] == self.algorithm.digest_size
+ self._backend._lib.HMAC_CTX_cleanup(self._ctx)
+ return self._backend._ffi.buffer(buf)[:outlen[0]]
diff --git a/cryptography/hazmat/backends/openssl/rsa.py b/cryptography/hazmat/backends/openssl/rsa.py
index 2fada1b..a62a89f 100644
--- a/cryptography/hazmat/backends/openssl/rsa.py
+++ b/cryptography/hazmat/backends/openssl/rsa.py
@@ -422,7 +422,11 @@
assert res == 1
self._evp_pkey = evp_pkey
- self.key_size = self._backend._lib.BN_num_bits(self._rsa_cdata.n)
+ self._key_size = self._backend._lib.BN_num_bits(self._rsa_cdata.n)
+
+ @property
+ def key_size(self):
+ return self._key_size
def signer(self, padding, algorithm):
return _RSASignatureContext(self._backend, self, padding, algorithm)
@@ -474,7 +478,11 @@
assert res == 1
self._evp_pkey = evp_pkey
- self.key_size = self._backend._lib.BN_num_bits(self._rsa_cdata.n)
+ self._key_size = self._backend._lib.BN_num_bits(self._rsa_cdata.n)
+
+ @property
+ def key_size(self):
+ return self._key_size
def verifier(self, signature, padding, algorithm):
return _RSAVerificationContext(
diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst
index 4337b47..f5d6c19 100644
--- a/docs/development/getting-started.rst
+++ b/docs/development/getting-started.rst
@@ -2,10 +2,10 @@
===============
Working on ``cryptography`` requires the installation of a small number of
-development dependencies in addition to the dependencies for :doc:`/installation`.
-These are listed in ``dev-requirements.txt`` and they can be installed in a
-`virtualenv`_ using `pip`_. Once you've installed the dependencies, install
-``cryptography`` in ``editable`` mode. For example:
+development dependencies in addition to the dependencies for
+:doc:`/installation`. These are listed in ``dev-requirements.txt`` and they can
+be installed in a `virtualenv`_ using `pip`_. Once you've installed the
+dependencies, install ``cryptography`` in ``editable`` mode. For example:
.. code-block:: console
@@ -13,6 +13,9 @@
$ pip install --requirement dev-requirements.txt
$ pip install --editable .
+You will also need to install ``enchant`` using your system's package manager
+to check spelling in the documentation.
+
You are now ready to run the tests and build the documentation.
Running tests
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index 5dc7e2f..4b3c460 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -116,7 +116,7 @@
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.asymmetric import ec
>>> private_key = ec.generate_private_key(
- ... ec.SECT283K1(), default_backend()
+ ... ec.SECP384R1(), default_backend()
... )
>>> signer = private_key.signer(ec.ECDSA(hashes.SHA256()))
>>> signer.update(b"this is some data I'd like")
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index bcb1fb3..abc2b07 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -430,9 +430,9 @@
make a message the correct size. ``CipherContext`` will not automatically
apply any padding; you'll need to add your own. For block ciphers the
recommended padding is
- :class:`cryptography.hazmat.primitives.padding.PKCS7`. If you are using a
+ :class:`~cryptography.hazmat.primitives.padding.PKCS7`. If you are using a
stream cipher mode (such as
- :class:`cryptography.hazmat.primitives.modes.CTR`) you don't have to worry
+ :class:`~cryptography.hazmat.primitives.modes.CTR`) you don't have to worry
about this.
.. method:: update(data)
@@ -443,7 +443,7 @@
When the ``Cipher`` was constructed in a mode that turns it into a
stream cipher (e.g.
- :class:`cryptography.hazmat.primitives.ciphers.modes.CTR`), this will
+ :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`), this will
return bytes immediately, however in other modes it will return chunks
whose size is determined by the cipher's block size.
diff --git a/docs/limitations.rst b/docs/limitations.rst
index 5b63ef5..ce61d89 100644
--- a/docs/limitations.rst
+++ b/docs/limitations.rst
@@ -10,10 +10,10 @@
uninitialized memory.
Python exposes no API for us to implement this reliably and as such almost all
-software in Python is potentially vulnerable to this attack. However the
-`CERT secure coding guidelines`_ consider this issue as "low severity,
-unlikely, expensive to repair" and we do not consider this a high risk for most
-users.
+software in Python is potentially vulnerable to this attack. The
+`CERT secure coding guidelines`_ assesses this issue as "Severity: medium,
+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
.. _`CERT secure coding guidelines`: https://www.securecoding.cert.org/confluence/display/seccode/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources