diff options
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 204 | ||||
-rw-r--r-- | cryptography/hazmat/backends/openssl/ec.py | 175 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/err.py | 19 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/dsa.py | 71 | ||||
-rw-r--r-- | docs/development/c-bindings.rst | 192 | ||||
-rw-r--r-- | docs/development/index.rst | 1 | ||||
-rw-r--r-- | docs/development/submitting-patches.rst | 66 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/dsa.rst | 67 | ||||
-rw-r--r-- | docs/hazmat/primitives/interfaces.rst | 89 | ||||
-rw-r--r-- | docs/spelling_wordlist.txt | 4 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 5 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_dsa.py | 60 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_ec.py | 29 |
13 files changed, 683 insertions, 299 deletions
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 53d92be3..5a1a2118 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -30,6 +30,10 @@ from cryptography.hazmat.backends.interfaces import ( HashBackend, PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend, TraditionalOpenSSLSerializationBackend ) +from cryptography.hazmat.backends.openssl.ec import ( + _ECDSASignatureContext, _ECDSAVerificationContext, + _EllipticCurvePrivateKey, _EllipticCurvePublicKey +) from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa @@ -930,8 +934,28 @@ class Backend(object): if self._lib.Cryptography_HAS_EC != 1: return False - curves = self._supported_curves() - return curve.name.encode("ascii") in curves + try: + curve_nid = self._elliptic_curve_to_nid(curve) + except UnsupportedAlgorithm: + curve_nid = self._lib.NID_undef + + ctx = self._lib.EC_GROUP_new_by_curve_name(curve_nid) + + if ctx == self._ffi.NULL: + errors = self._consume_errors() + assert ( + curve_nid == self._lib.NID_undef or + errors[0][1:] == ( + self._lib.ERR_LIB_EC, + self._lib.EC_F_EC_GROUP_NEW_BY_CURVE_NAME, + self._lib.EC_R_UNKNOWN_GROUP + ) + ) + return False + else: + assert curve_nid != self._lib.NID_undef + self._lib.EC_GROUP_free(ctx) + return True def elliptic_curve_signature_algorithm_supported( self, signature_algorithm, curve @@ -952,30 +976,6 @@ class Backend(object): return self.elliptic_curve_supported(curve) - def _supported_curves(self): - if self._lib.Cryptography_HAS_EC != 1: - return [] - - num_curves = self._lib.EC_get_builtin_curves(self._ffi.NULL, 0) - curve_array = self._ffi.new("EC_builtin_curve[]", num_curves) - num_curves_assigned = self._lib.EC_get_builtin_curves( - curve_array, num_curves) - assert num_curves == num_curves_assigned - - curves = [ - self._ffi.string(self._lib.OBJ_nid2sn(curve.nid)).decode() - for curve in curve_array - ] - - curve_aliases = { - "prime192v1": "secp192r1", - "prime256v1": "secp256r1" - } - return [ - curve_aliases.get(curve, curve) - for curve in curves - ] - def _create_ecdsa_signature_ctx(self, private_key, ecdsa): return _ECDSASignatureContext(self, private_key, ecdsa.algorithm) @@ -1983,158 +1983,6 @@ class _CMACContext(object): ) -def _truncate_digest_for_ecdsa(ec_key_cdata, digest, backend): - _lib = backend._lib - _ffi = backend._ffi - - digest_len = len(digest) - - group = _lib.EC_KEY_get0_group(ec_key_cdata) - - bn_ctx = _lib.BN_CTX_new() - assert bn_ctx != _ffi.NULL - bn_ctx = _ffi.gc(bn_ctx, _lib.BN_CTX_free) - - order = _lib.BN_CTX_get(bn_ctx) - assert order != _ffi.NULL - - res = _lib.EC_GROUP_get_order(group, order, bn_ctx) - assert res == 1 - - order_bits = _lib.BN_num_bits(order) - - if 8 * digest_len > order_bits: - digest_len = (order_bits + 7) // 8 - digest = digest[:digest_len] - - if 8 * digest_len > order_bits: - rshift = 8 - (order_bits & 0x7) - assert rshift > 0 and rshift < 8 - - mask = 0xFF >> rshift << rshift - - # Set the bottom rshift bits to 0 - digest = digest[:-1] + six.int2byte(six.byte2int(digest[-1]) & mask) - - return digest - - -@utils.register_interface(interfaces.AsymmetricSignatureContext) -class _ECDSASignatureContext(object): - def __init__(self, backend, private_key, algorithm): - self._backend = backend - self._private_key = private_key - self._digest = hashes.Hash(algorithm, backend) - - def update(self, data): - self._digest.update(data) - - def finalize(self): - ec_key = self._private_key._ec_key - - digest = self._digest.finalize() - - digest = _truncate_digest_for_ecdsa(ec_key, digest, self._backend) - - max_size = self._backend._lib.ECDSA_size(ec_key) - assert max_size > 0 - - sigbuf = self._backend._ffi.new("char[]", max_size) - siglen_ptr = self._backend._ffi.new("unsigned int[]", 1) - res = self._backend._lib.ECDSA_sign( - 0, - digest, - len(digest), - sigbuf, - siglen_ptr, - ec_key - ) - assert res == 1 - return self._backend._ffi.buffer(sigbuf)[:siglen_ptr[0]] - - -@utils.register_interface(interfaces.AsymmetricVerificationContext) -class _ECDSAVerificationContext(object): - def __init__(self, backend, public_key, signature, algorithm): - self._backend = backend - self._public_key = public_key - self._signature = signature - self._digest = hashes.Hash(algorithm, backend) - - def update(self, data): - self._digest.update(data) - - def verify(self): - ec_key = self._public_key._ec_key - - digest = self._digest.finalize() - - digest = _truncate_digest_for_ecdsa(ec_key, digest, self._backend) - - res = self._backend._lib.ECDSA_verify( - 0, - digest, - len(digest), - self._signature, - len(self._signature), - ec_key - ) - if res != 1: - self._backend._consume_errors() - raise InvalidSignature - return True - - -@utils.register_interface(interfaces.EllipticCurvePrivateKey) -class _EllipticCurvePrivateKey(object): - def __init__(self, backend, ec_key_cdata, curve): - self._backend = backend - self._ec_key = ec_key_cdata - self._curve = curve - - @property - def curve(self): - return self._curve - - def signer(self, signature_algorithm): - if isinstance(signature_algorithm, ec.ECDSA): - return self._backend._create_ecdsa_signature_ctx( - self, signature_algorithm) - else: - raise UnsupportedAlgorithm( - "Unsupported elliptic curve signature algorithm.", - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) - - def public_key(self): - public_ec_key = self._backend._public_ec_key_from_private_ec_key( - self._ec_key - ) - - return _EllipticCurvePublicKey( - self._backend, public_ec_key, self._curve) - - -@utils.register_interface(interfaces.EllipticCurvePublicKey) -class _EllipticCurvePublicKey(object): - def __init__(self, backend, ec_key_cdata, curve): - self._backend = backend - self._ec_key = ec_key_cdata - self._curve = curve - - @property - def curve(self): - return self._curve - - def verifier(self, signature, signature_algorithm): - if isinstance(signature_algorithm, ec.ECDSA): - return self._backend._create_ecdsa_verification_ctx( - self, signature, signature_algorithm) - else: - raise UnsupportedAlgorithm( - "Unsupported elliptic curve signature algorithm.", - _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) - - @utils.register_interface(RSAPrivateKeyWithNumbers) class _RSAPrivateKey(object): def __init__(self, backend, rsa_cdata): diff --git a/cryptography/hazmat/backends/openssl/ec.py b/cryptography/hazmat/backends/openssl/ec.py new file mode 100644 index 00000000..892d20ea --- /dev/null +++ b/cryptography/hazmat/backends/openssl/ec.py @@ -0,0 +1,175 @@ +# 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 + +import six + +from cryptography import utils +from cryptography.exceptions import ( + InvalidSignature, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.asymmetric import ec + + +def _truncate_digest_for_ecdsa(ec_key_cdata, digest, backend): + _lib = backend._lib + _ffi = backend._ffi + + digest_len = len(digest) + + group = _lib.EC_KEY_get0_group(ec_key_cdata) + + bn_ctx = _lib.BN_CTX_new() + assert bn_ctx != _ffi.NULL + bn_ctx = _ffi.gc(bn_ctx, _lib.BN_CTX_free) + + order = _lib.BN_CTX_get(bn_ctx) + assert order != _ffi.NULL + + res = _lib.EC_GROUP_get_order(group, order, bn_ctx) + assert res == 1 + + order_bits = _lib.BN_num_bits(order) + + if 8 * digest_len > order_bits: + digest_len = (order_bits + 7) // 8 + digest = digest[:digest_len] + + if 8 * digest_len > order_bits: + rshift = 8 - (order_bits & 0x7) + assert rshift > 0 and rshift < 8 + + mask = 0xFF >> rshift << rshift + + # Set the bottom rshift bits to 0 + digest = digest[:-1] + six.int2byte(six.indexbytes(digest, -1) & mask) + + return digest + + +@utils.register_interface(interfaces.AsymmetricSignatureContext) +class _ECDSASignatureContext(object): + def __init__(self, backend, private_key, algorithm): + self._backend = backend + self._private_key = private_key + self._digest = hashes.Hash(algorithm, backend) + + def update(self, data): + self._digest.update(data) + + def finalize(self): + ec_key = self._private_key._ec_key + + digest = self._digest.finalize() + + digest = _truncate_digest_for_ecdsa(ec_key, digest, self._backend) + + max_size = self._backend._lib.ECDSA_size(ec_key) + assert max_size > 0 + + sigbuf = self._backend._ffi.new("char[]", max_size) + siglen_ptr = self._backend._ffi.new("unsigned int[]", 1) + res = self._backend._lib.ECDSA_sign( + 0, + digest, + len(digest), + sigbuf, + siglen_ptr, + ec_key + ) + assert res == 1 + return self._backend._ffi.buffer(sigbuf)[:siglen_ptr[0]] + + +@utils.register_interface(interfaces.AsymmetricVerificationContext) +class _ECDSAVerificationContext(object): + def __init__(self, backend, public_key, signature, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + self._digest = hashes.Hash(algorithm, backend) + + def update(self, data): + self._digest.update(data) + + def verify(self): + ec_key = self._public_key._ec_key + + digest = self._digest.finalize() + + digest = _truncate_digest_for_ecdsa(ec_key, digest, self._backend) + + res = self._backend._lib.ECDSA_verify( + 0, + digest, + len(digest), + self._signature, + len(self._signature), + ec_key + ) + if res != 1: + self._backend._consume_errors() + raise InvalidSignature + return True + + +@utils.register_interface(interfaces.EllipticCurvePrivateKey) +class _EllipticCurvePrivateKey(object): + def __init__(self, backend, ec_key_cdata, curve): + self._backend = backend + self._ec_key = ec_key_cdata + self._curve = curve + + @property + def curve(self): + return self._curve + + def signer(self, signature_algorithm): + if isinstance(signature_algorithm, ec.ECDSA): + return self._backend._create_ecdsa_signature_ctx( + self, signature_algorithm) + else: + raise UnsupportedAlgorithm( + "Unsupported elliptic curve signature algorithm.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def public_key(self): + public_ec_key = self._backend._public_ec_key_from_private_ec_key( + self._ec_key + ) + + return _EllipticCurvePublicKey( + self._backend, public_ec_key, self._curve) + + +@utils.register_interface(interfaces.EllipticCurvePublicKey) +class _EllipticCurvePublicKey(object): + def __init__(self, backend, ec_key_cdata, curve): + self._backend = backend + self._ec_key = ec_key_cdata + self._curve = curve + + @property + def curve(self): + return self._curve + + def verifier(self, signature, signature_algorithm): + if isinstance(signature_algorithm, ec.ECDSA): + return self._backend._create_ecdsa_verification_ctx( + self, signature, signature_algorithm) + else: + raise UnsupportedAlgorithm( + "Unsupported elliptic curve signature algorithm.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index 9c9b45c2..232060a2 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -21,6 +21,7 @@ TYPES = """ static const int Cryptography_HAS_REMOVE_THREAD_STATE; static const int Cryptography_HAS_098H_ERROR_CODES; static const int Cryptography_HAS_098C_CAMELLIA_CODES; +static const int Cryptography_HAS_EC_CODES; struct ERR_string_data_st { unsigned long error; @@ -29,6 +30,7 @@ struct ERR_string_data_st { typedef struct ERR_string_data_st ERR_STRING_DATA; static const int ERR_LIB_EVP; +static const int ERR_LIB_EC; static const int ERR_LIB_PEM; static const int ERR_LIB_ASN1; static const int ERR_LIB_RSA; @@ -172,6 +174,10 @@ static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; static const int EVP_R_WRONG_FINAL_BLOCK_LENGTH; static const int EVP_R_WRONG_PUBLIC_KEY_TYPE; +static const int EC_F_EC_GROUP_NEW_BY_CURVE_NAME; + +static const int EC_R_UNKNOWN_GROUP; + static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO; static const int PEM_F_D2I_PKCS8PRIVATEKEY_FP; static const int PEM_F_DO_PK8PKEY; @@ -306,6 +312,15 @@ static const long Cryptography_HAS_098C_CAMELLIA_CODES = 0; static const int EVP_F_CAMELLIA_INIT_KEY = 0; static const int EVP_R_CAMELLIA_KEY_SETUP_FAILED = 0; #endif + +// OpenSSL without EC. e.g. RHEL +#ifndef OPENSSL_NO_EC +static const long Cryptography_HAS_EC_CODES = 1; +#else +static const long Cryptography_HAS_EC_CODES = 0; +static const int EC_R_UNKNOWN_GROUP = 0; +static const int EC_F_EC_GROUP_NEW_BY_CURVE_NAME = 0; +#endif """ CONDITIONAL_NAMES = { @@ -324,5 +339,9 @@ CONDITIONAL_NAMES = { "Cryptography_HAS_098C_CAMELLIA_CODES": [ "EVP_F_CAMELLIA_INIT_KEY", "EVP_R_CAMELLIA_KEY_SETUP_FAILED" + ], + "Cryptography_HAS_EC_CODES": [ + "EC_R_UNKNOWN_GROUP", + "EC_F_EC_GROUP_NEW_BY_CURVE_NAME" ] } diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index a9ae9ecb..4d78679e 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -181,3 +181,74 @@ class DSAPublicKey(object): def parameters(self): return DSAParameters(self._modulus, self._subgroup_order, self._generator) + + +class DSAParameterNumbers(object): + def __init__(self, p, q, g): + if ( + not isinstance(p, six.integer_types) or + not isinstance(q, six.integer_types) or + not isinstance(g, six.integer_types) + ): + raise TypeError( + "DSAParameterNumbers p, q, and g arguments must be integers." + ) + + self._p = p + self._q = q + self._g = g + + @property + def p(self): + return self._p + + @property + def q(self): + return self._q + + @property + def g(self): + return self._g + + +class DSAPublicNumbers(object): + def __init__(self, y, parameter_numbers): + if not isinstance(y, six.integer_types): + raise TypeError("DSAPublicNumbers y argument must be an integer.") + + if not isinstance(parameter_numbers, DSAParameterNumbers): + raise TypeError( + "parameter_numbers must be a DSAParameterNumbers instance." + ) + + self._y = y + self._parameter_numbers = parameter_numbers + + @property + def y(self): + return self._y + + @property + def parameter_numbers(self): + return self._parameter_numbers + + +class DSAPrivateNumbers(object): + def __init__(self, x, public_numbers): + if not isinstance(x, six.integer_types): + raise TypeError("DSAPrivateNumbers x argument must be an integer.") + + if not isinstance(public_numbers, DSAPublicNumbers): + raise TypeError( + "public_numbers must be a DSAPublicNumbers instance." + ) + self._public_numbers = public_numbers + self._x = x + + @property + def x(self): + return self._x + + @property + def public_numbers(self): + return self._public_numbers diff --git a/docs/development/c-bindings.rst b/docs/development/c-bindings.rst new file mode 100644 index 00000000..56963379 --- /dev/null +++ b/docs/development/c-bindings.rst @@ -0,0 +1,192 @@ +C bindings +========== + +C bindings are bindings to C libraries, using cffi_ whenever possible. + +.. _cffi: http://cffi.readthedocs.org + +Bindings live in :py:mod:`cryptography.hazmat.bindings`. + +Style guide +----------- + +Don't name parameters: + +.. code-block:: c + + /* Good */ + long f(long); + /* Bad */ + long f(long x); + +...unless they're inside a struct: + +.. code-block:: c + + struct my_struct { + char *name; + int number; + ...; + }; + +Include ``void`` if the function takes no arguments: + +.. code-block:: c + + /* Good */ + long f(void); + /* Bad */ + long f(); + +Wrap lines at 80 characters like so: + +.. code-block:: c + + /* Pretend this went to 80 characters */ + long f(long, long, + int *) + +Include a space after commas between parameters: + +.. code-block:: c + + /* Good */ + long f(int, char *) + /* Bad */ + long f(int,char *) + +Use C-style ``/* */`` comments instead of C++-style ``//``: + +.. code-block:: c + + // Bad + /* Good */ + +Values set by ``#define`` should be assigned the appropriate type. If you see +this: + +.. code-block:: c + + #define SOME_INTEGER_LITERAL 0x0; + #define SOME_UNSIGNED_INTEGER_LITERAL 0x0001U; + #define SOME_STRING_LITERAL "hello"; + +...it should be added to the bindings like so: + +.. code-block:: c + + static const int SOME_INTEGER_LITERAL; + static const unsigned int SOME_UNSIGNED_INTEGER_LITERAL; + static const char *const SOME_STRING_LITERAL; + +Adding constant, types, functions... +------------------------------------ + +You can create bindings for any name that exists in some version of +the library you're binding against. However, the project also has to +keep supporting older versions of the library. In order to achieve +this, binding modules have ``CUSTOMIZATIONS`` and +``CONDITIONAL_NAMES`` constants. + +Let's say you want to enable quantum transmogrification. The upstream +library implements this as the following API:: + + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT; + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT; + typedef ... QM_TRANSMOGRIFICATION_CTX; + int QM_transmogrify(QM_TRANSMOGRIFICATION_CTX *, int); + +To start, create a new constant that defines if the *actual* library +has the feature you want, and add it to ``TYPES``:: + + static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION; + +This should start with ``Cryptography_``, since we're adding it in +this library. This prevents namespace collisions. + +Then, define the actual features (constants, types, functions...) you +want to expose. If it's a constant, just add it to ``TYPES``:: + + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT; + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT; + +If it's a struct, add it to ``TYPES`` as well. The following is an +opaque struct:: + + typedef ... QM_TRANSMOGRIFICATION_CTX; + +... but you can also make some or all items in the struct accessible:: + + typedef struct { + /* Fundamental constant k for your particular universe */ + BIGNUM *k; + ...; + } QM_TRANSMOGRIFICATION_CTX; + +Confusingly, functions that aren't always available on all supported +versions of the library, should be defined in ``MACROS`` and *not* in +``FUNCTIONS``. Fortunately, you just have to copy the signature:: + + int QM_transmogrify(QM_TRANSMOGRIFICATION_CTX *, int); + +Then, we define the ``CUSTOMIZATIONS`` entry. To do that, we have to +come up with a C preprocessor expression that decides whether or not a +feature exists in the library. For example:: + + #ifdef QM_transmogrify + +Then, we set the flag that signifies the feature exists:: + + static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION = 1; + +Otherwise, we set that flag to 0:: + + #else + static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION = 0; + +Then, in that ``#else`` block, we define the names that aren't +available as dummy values. For an integer constant, use 0:: + + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT = 0; + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT = 0; + +For a function, it's a bit trickier. You have to define a function +pointer of the appropriate type to be NULL:: + + int (*QM_transmogrify)(QM_TRANSMOGRIFICATION_CTX *, int) = NULL; + +(To do that, copy the signature, put a ``*`` in front of the function +name and wrap it in parentheses, and then put ``= NULL`` at the end). + +Note how types don't need to be conditionally defined, as long as all +the necessarily type definitions are in place. + +Finally, add an entry to ``CONDITIONAL_NAMES`` with all of the things +you want to conditionally export:: + + CONDITIONAL_NAMES = { + ... + "Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION": [ + "QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT", + "QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT", + "QM_transmogrify" + ] + } + +Caveats +~~~~~~~ + +Sometimes, a set of loosely related features are added in the same +version, and it's impractical to create ``#ifdef`` statements for each +one. In that case, it may make sense to either check for a particular +version. For example, to check for OpenSSL 1.0.0 or newer:: + + #if OPENSSL_VERSION_NUMBER >= 0x10000000L + +Sometimes, the version of a library on a particular platform will have +features that you thought it wouldn't, based on its version. +Occasionally, packagers appear to ship arbitrary VCS checkouts. As a +result, sometimes you may have to add separate ``#ifdef`` statements +for particular features. This kind of issue is typically only caught +by running the tests on a wide variety of systems, which is the job of +our continuous integration infrastructure. diff --git a/docs/development/index.rst b/docs/development/index.rst index 50b60900..f9bc9eea 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -14,6 +14,7 @@ bug check out `what to put in your bug report`_. submitting-patches reviewing-patches test-vectors + c-bindings .. _`GitHub`: https://github.com/pyca/cryptography .. _`what to put in your bug report`: http://www.contribution-guide.org/#what-to-put-in-your-bug-report diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst index b7f43283..fe2df431 100644 --- a/docs/development/submitting-patches.rst +++ b/docs/development/submitting-patches.rst @@ -75,70 +75,8 @@ specifying a different backend. C bindings ~~~~~~~~~~ -When binding C code with ``cffi`` we have our own style guide, it's pretty -simple. - -Don't name parameters: - -.. code-block:: c - - // Good - long f(long); - // Bad - long f(long x); - -...unless they're inside a struct: - -.. code-block:: c - - struct my_struct { - char *name; - int number; - ...; - }; - -Include ``void`` if the function takes no arguments: - -.. code-block:: c - - // Good - long f(void); - // Bad - long f(); - -Wrap lines at 80 characters like so: - -.. code-block:: c - - // Pretend this went to 80 characters - long f(long, long, - int *) - -Include a space after commas between parameters: - -.. code-block:: c - - // Good - long f(int, char *) - // Bad - long f(int,char *) - -Values set by ``#define`` should be assigned the appropriate type. If you see -this: - -.. code-block:: c - - #define SOME_INTEGER_LITERAL 0x0; - #define SOME_UNSIGNED_INTEGER_LITERAL 0x0001U; - #define SOME_STRING_LITERAL "hello"; - -...it should be added to the bindings like so: - -.. code-block:: c - - static const int SOME_INTEGER_LITERAL; - static const unsigned int SOME_UNSIGNED_INTEGER_LITERAL; - static const char *const SOME_STRING_LITERAL; +More information on C bindings can be found in :doc:`the dedicated +section of the documentation <c-bindings>`. Tests ----- diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 6848d84c..2167e528 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -210,6 +210,73 @@ DSA :returns: :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` +.. class:: DSAParameterNumbers(p, q, g) + + .. versionadded:: 0.5 + + The collection of integers that make up a set of DSA parameters. + + .. attribute:: p + + :type: int + + The public modulus. + + .. attribute:: q + + :type: int + + The sub-group order. + + .. attribute:: g + + :type: int + + The generator. + +.. class:: DSAPublicNumbers(y, parameter_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up a DSA public key. + + .. attribute:: y + + :type: int + + The public value ``y``. + + .. attribute:: parameter_numbers + + :type: :class:`~cryptography.hazmat.primitives.dsa.DSAParameterNumbers` + + The :class:`~cryptography.hazmat.primitives.dsa.DSAParameterNumbers` + associated with the public key. + +.. class:: DSAPrivateNumbers(x, public_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up a DSA private key. + + .. warning:: + + Revealing the value of ``x`` will compromise the security of any + cryptographic operations performed. + + .. attribute:: x + + :type: int + + The private value ``x``. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.dsa.DSAPublicNumbers` + + The :class:`~cryptography.hazmat.primitives.dsa.DSAPublicNumbers` + associated with the private key. + .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index b8aca0c2..0c456326 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -13,7 +13,7 @@ to document argument and return types. Symmetric ciphers -~~~~~~~~~~~~~~~~~ +----------------- .. currentmodule:: cryptography.hazmat.primitives.interfaces @@ -48,7 +48,7 @@ Symmetric ciphers Cipher modes ------------- +~~~~~~~~~~~~ Interfaces used by the symmetric cipher modes described in :ref:`Symmetric Encryption Modes <symmetric-encryption-modes>`. @@ -104,7 +104,44 @@ Interfaces used by the symmetric cipher modes described in individual modes. Asymmetric interfaces -~~~~~~~~~~~~~~~~~~~~~ +--------------------- + +.. class:: AsymmetricSignatureContext + + .. versionadded:: 0.2 + + .. method:: update(data) + + :param bytes data: The data you want to sign. + + .. method:: finalize() + + :return bytes signature: The signature. + + +.. class:: AsymmetricVerificationContext + + .. versionadded:: 0.2 + + .. method:: update(data) + + :param bytes data: The data you wish to verify using the signature. + + .. method:: verify() + + :raises cryptography.exceptions.InvalidSignature: If the signature does + not validate. + + +.. class:: AsymmetricPadding + + .. versionadded:: 0.2 + + .. attribute:: name + + +RSA +~~~ .. class:: RSAPrivateKey @@ -236,6 +273,9 @@ Asymmetric interfaces instance. +DSA +~~~ + .. class:: DSAParameters .. versionadded:: 0.3 @@ -458,6 +498,9 @@ Asymmetric interfaces The bit length of the curve's base point. +Elliptic Curve +~~~~~~~~~~~~~~ + .. class:: EllipticCurveSignatureAlgorithm .. versionadded:: 0.5 @@ -526,42 +569,8 @@ Asymmetric interfaces The elliptic curve for this key. -.. class:: AsymmetricSignatureContext - - .. versionadded:: 0.2 - - .. method:: update(data) - - :param bytes data: The data you want to sign. - - .. method:: finalize() - - :return bytes signature: The signature. - - -.. class:: AsymmetricVerificationContext - - .. versionadded:: 0.2 - - .. method:: update(data) - - :param bytes data: The data you wish to verify using the signature. - - .. method:: verify() - - :raises cryptography.exceptions.InvalidSignature: If the signature does - not validate. - - -.. class:: AsymmetricPadding - - .. versionadded:: 0.2 - - .. attribute:: name - - Hash algorithms -~~~~~~~~~~~~~~~ +--------------- .. class:: HashAlgorithm @@ -607,7 +616,7 @@ Hash algorithms Key derivation functions -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ .. class:: KeyDerivationFunction @@ -652,7 +661,7 @@ Key derivation functions `CMAC`_ -~~~~~~~ +------- .. class:: CMACContext diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index e7a63f26..dc123493 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -30,8 +30,12 @@ invariants iOS Koblitz metadata +namespace +namespaces pickleable plaintext +preprocessor +preprocessors pseudorandom Schneier scrypt diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 0dd91695..bd99c8f2 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -487,11 +487,6 @@ class TestOpenSSLNoEllipticCurve(object): None, None ) is False - def test_supported_curves(self, monkeypatch): - monkeypatch.setattr(backend._lib, "Cryptography_HAS_EC", 0) - - assert backend._supported_curves() == [] - class TestDeprecatedRSABackendMethods(object): def test_create_rsa_signature_ctx(self): diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index cbe10e9c..51a03c48 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -872,3 +872,63 @@ def test_dsa_generate_invalid_backend(): with raises_unsupported_algorithm( _Reasons.BACKEND_MISSING_INTERFACE): dsa.DSAPrivateKey.generate(pretend_parameters, pretend_backend) + + +class TestDSANumbers(object): + def test_dsa_parameter_numbers(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + assert parameter_numbers.p == 1 + assert parameter_numbers.q == 2 + assert parameter_numbers.g == 3 + + def test_dsa_parameter_numbers_invalid_types(self): + with pytest.raises(TypeError): + dsa.DSAParameterNumbers(p=None, q=2, g=3) + + with pytest.raises(TypeError): + dsa.DSAParameterNumbers(p=1, q=None, g=3) + + with pytest.raises(TypeError): + dsa.DSAParameterNumbers(p=1, q=2, g=None) + + def test_dsa_public_numbers(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + assert public_numbers.y == 4 + assert public_numbers.parameter_numbers == parameter_numbers + + def test_dsa_public_numbers_invalid_types(self): + with pytest.raises(TypeError): + dsa.DSAPublicNumbers(y=4, parameter_numbers=None) + + with pytest.raises(TypeError): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + dsa.DSAPublicNumbers(y=None, parameter_numbers=parameter_numbers) + + def test_dsa_private_numbers(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + private_numbers = dsa.DSAPrivateNumbers( + x=5, + public_numbers=public_numbers + ) + assert private_numbers.x == 5 + assert private_numbers.public_numbers == public_numbers + + def test_dsa_private_numbers_invalid_types(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + with pytest.raises(TypeError): + dsa.DSAPrivateNumbers(x=4, public_numbers=None) + + with pytest.raises(TypeError): + dsa.DSAPrivateNumbers(x=None, public_numbers=public_numbers) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 1879f4fc..2690e794 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -70,6 +70,15 @@ def _skip_ecdsa_vector(backend, curve_type, hash_type): ) +def _skip_curve_unsupported(backend, curve): + if not backend.elliptic_curve_supported(curve): + pytest.skip( + "Curve {0} is not supported by this backend {1}".format( + curve.name, backend + ) + ) + + @utils.register_interface(interfaces.EllipticCurve) class DummyCurve(object): name = "dummy-curve" @@ -81,6 +90,12 @@ class DummySignatureAlgorithm(object): pass +@pytest.mark.elliptic +def test_skip_curve_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_curve_unsupported(backend, DummyCurve()) + + def test_ec_numbers(): numbers = ec.EllipticCurvePrivateNumbers( 1, @@ -176,12 +191,7 @@ class TestECDSAVectors(object): "curve", _CURVE_TYPES.values() ) def test_generate_vector_curves(self, backend, curve): - if not backend.elliptic_curve_supported(curve()): - pytest.skip( - "Curve {0} is not supported by this backend {1}".format( - curve().name, backend - ) - ) + _skip_curve_unsupported(backend, curve()) key = ec.generate_private_key(curve(), backend) assert key @@ -205,12 +215,7 @@ class TestECDSAVectors(object): ) is False def test_unknown_signature_algoritm(self, backend): - if not backend.elliptic_curve_supported(ec.SECP192R1()): - pytest.skip( - "Curve secp192r1 is not supported by this backend {0}".format( - backend - ) - ) + _skip_curve_unsupported(backend, ec.SECP192R1()) key = ec.generate_private_key(ec.SECP192R1(), backend) |