1"""
2Implements interface to OpenSSL EVP_Digest* functions.
3
4Interface  made as close to hashlib as possible.
5
6This module is really an excess effort. Hashlib allows access to
7mostly same functionality except oids and nids of hashing
8algortithms (which might be needed for private key operations).
9
10hashlib even allows to use engine-provided digests if it is build
11with dinamically linked libcrypto - so use
12ctypescrypto.engine.set_default("gost",xFFFF) and md_gost94
13algorithm would be available both to this module and hashlib.
14
15"""
16from ctypes import c_int, c_char_p, c_void_p, POINTER, c_long, c_longlong
17from ctypes import create_string_buffer, byref
18from ctypescrypto import libcrypto,pyver, bintype
19from ctypescrypto.exception import LibCryptoError
20from ctypescrypto.oid import Oid
21DIGEST_ALGORITHMS = ("MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512")
22
23__all__ = ['DigestError', 'Digest', 'DigestType', 'new']
24
25class DigestError(LibCryptoError):
26    """ Exception raised if some OpenSSL function returns error """
27    pass
28
29def new(algname):
30    """
31    Behaves just like hashlib.new. Creates digest object by
32    algorithm name
33    """
34
35    digest_type = DigestType(algname)
36    return Digest(digest_type)
37
38class DigestType(object):
39    """
40    Represents EVP_MD object - constant structure which describes
41    digest algorithm
42    """
43    def __init__(self, digest_name):
44        """
45        Finds digest by its name. You can pass Oid object instead of
46        name.
47
48        Special case is when None is passed as name. In this case
49        unitialized digest is created, and can be initalized later
50        by setting its digest attribute to pointer to EVP_MD
51        """
52        if digest_name is None:
53            return
54
55        if isinstance(digest_name, Oid):
56            self.digest_name = digest_name.longname()
57        else:
58            self.digest_name = str(digest_name)
59        self.digest = libcrypto.EVP_get_digestbyname(self.digest_name.encode('us-ascii'))
60        if self.digest is None:
61            raise DigestError("Unknown digest: %s" % self.digest_name)
62
63    @property
64    def name(self):
65        """ Returns name of the digest """
66        if not hasattr(self, 'digest_name'):
67            self.digest_name = Oid(libcrypto.EVP_MD_type(self.digest)
68                                  ).longname()
69        return self.digest_name
70
71    def __del__(self):
72        """ Empty destructor for constant object """
73        pass
74
75    @property
76    def digest_size(self):
77        """ Returns size of digest """
78        return libcrypto.EVP_MD_size(self.digest)
79
80    @property
81    def block_size(self):
82        """ Returns block size of the digest """
83        return libcrypto.EVP_MD_block_size(self.digest)
84
85    @property
86    def oid(self):
87        """ Returns Oid object of digest type """
88        return Oid(libcrypto.EVP_MD_type(self.digest))
89
90class Digest(object):
91    """
92    Represents EVP_MD_CTX object which actually used to calculate
93    digests.
94    """
95
96    def __init__(self, digest_type):
97        """
98        Initializes digest using given type.
99        """
100        self.ctx = self.newctx()
101        if self.ctx is None:
102            raise DigestError("Unable to create digest context")
103        self.digest_out = None
104        self.digest_finalized = False
105        result = libcrypto.EVP_DigestInit_ex(self.ctx, digest_type.digest, None)
106        if result == 0:
107            self._clean_ctx()
108            raise DigestError("Unable to initialize digest")
109        self.digest_type = digest_type
110        self.digest_size = self.digest_type.digest_size
111        self.block_size = self.digest_type.block_size
112
113    def __del__(self):
114        """ Uses _clean_ctx internal method """
115        self._clean_ctx()
116
117    def update(self, data, length=None):
118        """
119        Hashes given byte string
120
121        @param data - string to hash
122        @param length - if not specifed, entire string is hashed,
123                otherwise only first length bytes
124        """
125        if self.digest_finalized:
126            raise DigestError("No updates allowed")
127        if not isinstance(data, bintype):
128            raise TypeError("A byte string is expected")
129        if length is None:
130            length = len(data)
131        elif length > len(data):
132            raise ValueError("Specified length is greater than length of data")
133        result = libcrypto.EVP_DigestUpdate(self.ctx, c_char_p(data), length)
134        if result != 1:
135            raise DigestError("Unable to update digest")
136
137    def digest(self, data=None):
138        """
139        Finalizes digest operation and return digest value
140        Optionally hashes more data before finalizing
141        """
142        if self.digest_finalized:
143            return self.digest_out.raw[:self.digest_size]
144        if data is not None:
145            self.update(data)
146        self.digest_out = create_string_buffer(256)
147        length = c_long(0)
148        result = libcrypto.EVP_DigestFinal_ex(self.ctx, self.digest_out,
149                                              byref(length))
150        if result != 1:
151            raise DigestError("Unable to finalize digest")
152        self.digest_finalized = True
153        return self.digest_out.raw[:self.digest_size]
154    def copy(self):
155        """
156        Creates copy of the digest CTX to allow to compute digest
157        while being able to hash more data
158        """
159
160        new_digest = Digest(self.digest_type)
161        libcrypto.EVP_MD_CTX_copy(new_digest.ctx, self.ctx)
162        return new_digest
163
164    def _clean_ctx(self):
165        """
166        Clears and deallocates context
167        """
168        try:
169            if self.ctx is not None:
170                libcrypto.EVP_MD_CTX_free(self.ctx)
171                del self.ctx
172        except AttributeError:
173            pass
174        self.digest_out = None
175        self.digest_finalized = False
176
177    def hexdigest(self, data=None):
178        """
179            Returns digest in the hexadecimal form. For compatibility
180            with hashlib
181        """
182        from base64 import b16encode
183        if pyver == 2:
184            return b16encode(self.digest(data))
185        else:
186            return b16encode(self.digest(data)).decode('us-ascii')
187
188
189# Declare function result and argument types
190libcrypto.EVP_get_digestbyname.restype = c_void_p
191libcrypto.EVP_get_digestbyname.argtypes = (c_char_p, )
192# These two functions are renamed in OpenSSL 1.1.0
193if hasattr(libcrypto,"EVP_MD_CTX_create"):
194    Digest.newctx = libcrypto.EVP_MD_CTX_create
195    Digest.freectx = libcrypto.EVP_MD_CTX_destroy
196else:
197    Digest.newctx = libcrypto.EVP_MD_CTX_new
198    Digest.freectx = libcrypto.EVP_MD_CTX_free
199Digest.newctx.restype = c_void_p
200Digest.freectx.argtypes = (c_void_p, )
201# libcrypto.EVP_MD_CTX_create has no arguments
202libcrypto.EVP_DigestInit_ex.restype = c_int
203libcrypto.EVP_DigestInit_ex.argtypes = (c_void_p, c_void_p, c_void_p)
204libcrypto.EVP_DigestUpdate.restype = c_int
205libcrypto.EVP_DigestUpdate.argtypes = (c_void_p, c_char_p, c_longlong)
206libcrypto.EVP_DigestFinal_ex.restype = c_int
207libcrypto.EVP_DigestFinal_ex.argtypes = (c_void_p, c_char_p, POINTER(c_long))
208libcrypto.EVP_MD_CTX_copy.restype = c_int
209libcrypto.EVP_MD_CTX_copy.argtypes = (c_void_p, c_void_p)
210libcrypto.EVP_MD_type.argtypes = (c_void_p, )
211libcrypto.EVP_MD_size.argtypes = (c_void_p, )
212libcrypto.EVP_MD_block_size.restype = c_int
213libcrypto.EVP_MD_block_size.argtypes = (c_void_p, )
214libcrypto.EVP_MD_size.restype = c_int
215libcrypto.EVP_MD_size.argtypes = (c_void_p, )
216libcrypto.EVP_MD_type.restype = c_int
217libcrypto.EVP_MD_type.argtypes = (c_void_p, )
218