From 8ae64e4dec332942cf428a8426b0062511de40f2 Mon Sep 17 00:00:00 2001 From: Mohammed Attia Date: Wed, 9 Apr 2014 23:28:09 +0200 Subject: Add DSA verification --- cryptography/hazmat/backends/interfaces.py | 25 +++++++ cryptography/hazmat/backends/openssl/backend.py | 91 ++++++++++++++++++++++++ cryptography/hazmat/primitives/asymmetric/dsa.py | 19 ++++- 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index aaaca5e2..3192789c 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -145,6 +145,31 @@ class DSABackend(object): a DSAParameters object. """ + @abc.abstractmethod + def create_dsa_verification_ctx(self, public_key, signature, algorithm): + """ + Returns an object conforming to the AsymmetricVerificationContext + interface. + """ + + @abc.abstractmethod + def dsa_signature_from_components(self, r, s): + """ + Convert a DSA signature pair r and s into a DER byte string. + """ + + @abc.abstractmethod + def dsa_hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported by the backend for DSA. + """ + + @abc.abstractmethod + def dsa_parameters_supported(self, p, q): + """ + Return True if the parameters are supported by the backend for DSA. + """ + @six.add_metaclass(abc.ABCMeta) class TraditionalOpenSSLSerializationBackend(object): diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index f9154f3b..bd1ce02b 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -474,6 +474,58 @@ class Backend(object): y=self._bn_to_int(ctx.pub_key) ) + def create_dsa_verification_ctx(self, public_key, signature, + algorithm): + return _DSAVerificationContext(self, public_key, signature, + algorithm) + + def dsa_signature_from_components(self, r, s): + dsa_sig = self._lib.DSA_SIG_new() + assert dsa_sig != self._ffi.NULL + dsa_sig = self._ffi.gc(dsa_sig, self._lib.DSA_SIG_free) + + dsa_sig.r = self._int_to_bn(r) + dsa_sig.s = self._int_to_bn(s) + + sig_len = self._lib.i2d_DSA_SIG(dsa_sig, self._ffi.NULL) + assert sig_len != 0 + + sig_buf = self._ffi.new("unsigned char []", sig_len) + sig_bufptr = self._ffi.new("unsigned char **", sig_buf) + + sig_len = self._lib.i2d_DSA_SIG(dsa_sig, sig_bufptr) + + return self._ffi.buffer(sig_buf)[:sig_len] + + def _dsa_cdata_from_public_key(self, public_key): + # Does not GC the DSA cdata. You *must* make sure it's freed + # correctly yourself! + ctx = self._lib.DSA_new() + assert ctx != self._ffi.NULL + parameters = public_key.parameters() + ctx.p = self._int_to_bn(parameters.p) + ctx.q = self._int_to_bn(parameters.q) + ctx.g = self._int_to_bn(parameters.g) + ctx.pub_key = self._int_to_bn(public_key.y) + return ctx + + def dsa_hash_supported(self, algorithm): + if ( + self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f and + not isinstance(algorithm, hashes.SHA1)): + return False + else: + return True + + def dsa_parameters_supported(self, p, q): + if (self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f + and not ( + utils.bit_length(p) <= 1024 + and utils.bit_length(q) <= 160)): + return False + else: + return True + def decrypt_rsa(self, private_key, ciphertext, padding): key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) if key_size_bytes != len(ciphertext): @@ -1297,6 +1349,45 @@ class _RSAVerificationContext(object): raise InvalidSignature +@utils.register_interface(interfaces.AsymmetricVerificationContext) +class _DSAVerificationContext(object): + def __init__( + self, backend, public_key, signature, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + self._algorithm = algorithm + + self._hash_ctx = _HashContext(backend, self._algorithm) + + def update(self, data): + if self._hash_ctx is None: + raise AlreadyFinalized("Context has already been finalized") + + self._hash_ctx.update(data) + + def verify(self): + if self._hash_ctx is None: + raise AlreadyFinalized("Context has already been finalized") + + self._dsa_cdata = self._backend._dsa_cdata_from_public_key( + self._public_key) + self._dsa_cdata = self._backend._ffi.gc(self._dsa_cdata, + self._backend._lib.DSA_free) + + data_to_verify = self._hash_ctx.finalize() + self._hash_ctx = None + + res = self._backend._lib.DSA_verify( + 0, data_to_verify, len(data_to_verify), self._signature, + len(self._signature), self._dsa_cdata) + + if res != 1: + errors = self._backend._consume_errors() + assert errors + raise InvalidSignature + + @utils.register_interface(interfaces.CMACContext) class _CMACContext(object): def __init__(self, backend, algorithm, ctx=None): diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 4c2de36a..0f7cf21a 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -18,7 +18,7 @@ import six from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import DSABackend -from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives import hashes, interfaces def _check_dsa_parameters(modulus, subgroup_order, generator): @@ -151,6 +151,23 @@ class DSAPublicKey(object): self._generator = generator self._y = y + def verifier(self, signature, algorithm, backend): + if not isinstance(backend, DSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement DSABackend", + _Reasons.BACKEND_MISSING_INTERFACE + ) + if not isinstance(algorithm, (hashes.SHA1, hashes.SHA224, + hashes.SHA256, hashes.SHA384, hashes.SHA512)): + raise UnsupportedAlgorithm( + "Unsupported Hash Algorithm, please use one of these: " + "SHA1, SHA224, SHA256, SHA384, SHA512", + _Reasons.UNSUPPORTED_HASH + ) + + return backend.create_dsa_verification_ctx(self, signature, + algorithm) + @property def key_size(self): return utils.bit_length(self._modulus) -- cgit v1.2.3