summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/softwares/memory/libkeepass/common.py
blob: 9a78a40b30a5bfae7e7dc844ac7888b49d2fe4c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# -*- coding: utf-8 -*-
import base64
import codecs
import io
import struct
from xml.etree import ElementTree

from .crypto import sha256

try:
    file_types = (file, io.IOBase)
except NameError:
    file_types = (io.IOBase,)


# file header
class HeaderDictionary(dict):
    """
    A dictionary on steroids for comfortable header field storage and
    manipulation.

    Header fields must be defined in the `fields` property before filling the
    dictionary with data. The `fields` property is a simple dictionary, where
    keys are field names (string) and values are field ids (int)::

        >>> h.fields['rounds'] = 4

    Now you can set and get values using the field id or the field name
    interchangeably::

        >>> h[4] = 3000
        >>> print h['rounds']
        3000
        >>> h['rounds'] = 6000
        >>> print h[4]
        6000

    It is also possible to get and set data using the field name as an
    attribute::

        >>> h.rounds = 9000
        >>> print h[4]
        9000
        >>> print h.rounds
        9000

    For some fields it is more comfortable to unpack their byte value into
    a numeric or character value (eg. the transformation rounds). For those
    fields add a format string to the `fmt` dictionary. Use the field id as
    key::

        >>> h.fmt[4] = '<q'

    Continue setting the value as before if you have it as a number and if you
    need it as a number, get it like before. Only when you have the packed value
    use a different interface::

        >>> h.b.rounds = '\x70\x17\x00\x00\x00\x00\x00\x00'
        >>> print h.b.rounds
        '\x70\x17\x00\x00\x00\x00\x00\x00'
        >>> print h.rounds
        6000

    The `b` (binary?) attribute is a special way to set and get data in its
    packed format, while the usual attribute or dictionary access allows
    setting and getting a numeric value::

        >>> h.rounds = 3000
        >>> print h.b.rounds
        '\xb8\x0b\x00\x00\x00\x00\x00\x00'
        >>> print h.rounds
        3000

    """
    fields = {}
    fmt = {}

    def __init__(self, *args):
        dict.__init__(self, *args)

    def __getitem__(self, key):
        if isinstance(key, int):
            return dict.__getitem__(self, key)
        else:
            return dict.__getitem__(self, self.fields[key])

    def __setitem__(self, key, val):
        if isinstance(key, int):
            dict.__setitem__(self, key, val)
        else:
            dict.__setitem__(self, self.fields[key], val)

    def __getattr__(self, key):
        class wrap(object):
            def __init__(self, d):
                object.__setattr__(self, 'd', d)

            def __getitem__(self, key):
                fmt = self.d.fmt.get(self.d.fields.get(key, key))
                if fmt:
                    return struct.pack(fmt, self.d[key])
                else:
                    return self.d[key]

            __getattr__ = __getitem__

            def __setitem__(self, key, val):
                fmt = self.d.fmt.get(self.d.fields.get(key, key))
                if fmt:
                    self.d[key] = struct.unpack(fmt, val)[0]
                else:
                    self.d[key] = val

            __setattr__ = __setitem__

        if key == 'b':
            return wrap(self)
        try:
            return self.__getitem__(key)
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, val):
        try:
            return self.__setitem__(key, val)
        except KeyError:
            return dict.__setattr__(self, key, val)


# file baseclass
class KDBFile(object):
    def __init__(self, stream=None, **credentials):
        # list of hashed credentials (pre-transformation)
        self.keys = []
        self.add_credentials(**credentials)

        # the buffer containing the decrypted/decompressed payload from a file
        self.in_buffer = None
        # the buffer filled with data for writing back to a file before
        # encryption/compression
        self.out_buffer = None
        # position in the `in_buffer` where the payload begins
        self.header_length = None
        # decryption success flag, set this to true upon verification of the
        # encryption masterkey. if this is True `in_buffer` must contain
        # clear data.
        self.opened = False

        # the raw/basic file handle, expect it to be closed after __init__!
        if stream is not None:
            if not isinstance(stream, io.IOBase):
                raise TypeError('Stream does not have the buffer interface.')
            self.read_from(stream)

    def read_from(self, stream):
        if not (isinstance(stream, io.IOBase) or isinstance(stream, file_types)):
            raise TypeError('Stream does not have the buffer interface.')
        self._read_header(stream)
        self._decrypt(stream)

    def _read_header(self, stream):
        raise NotImplementedError('The _read_header method was not '
                                  'implemented propertly.')

    def _decrypt(self, stream):
        self._make_master_key()
        # move read pointer beyond the file header
        if self.header_length is None:
            raise IOError('Header length unknown. Parse the header first!')
        stream.seek(self.header_length)

    def write_to(self, stream):
        raise NotImplementedError('The write_to() method was not implemented.')

    def add_credentials(self, **credentials):
        if credentials.get('password'):
            self.add_key_hash(sha256(credentials['password']))
        if credentials.get('keyfile'):
            self.add_key_hash(load_keyfile(credentials['keyfile']))

    def clear_credentials(self):
        """Remove all previously set encryption key hashes."""
        self.keys = []

    def add_key_hash(self, key_hash):
        """
        Add an encryption key hash, can be a hashed password or a hashed
        keyfile. Two things are important: must be SHA256 hashes and sequence is
        important: first password if any, second key file if any.
        """
        if key_hash is not None:
            self.keys.append(key_hash)

    def _make_master_key(self):
        if len(self.keys) == 0:
            raise IndexError('No credentials found.')

    def close(self):
        if self.in_buffer:
            self.in_buffer.close()

    def read(self, n=-1):
        """
        Read the decrypted and uncompressed data after the file header.
        For example, in KDB4 this would be plain, utf-8 xml.

        Note that this is the source data for the lxml.objectify element tree
        at `self.obj_root`. Any changes made to the parsed element tree will
        NOT be reflected in that data stream! Use `self.pretty_print` to get
        XML output from the element tree.
        """
        if self.in_buffer:
            return self.in_buffer.read(n)

    def seek(self, offset, whence=io.SEEK_SET):
        if self.in_buffer:
            return self.in_buffer.seek(offset, whence)

    def tell(self):
        if self.in_buffer:
            return self.in_buffer.tell()


# loading keyfiles
def load_keyfile(filename):
    try:
        return load_xml_keyfile(filename)
    except Exception:
        pass
    try:
        return load_plain_keyfile(filename)
    except Exception:
        pass


def load_xml_keyfile(filename):
    """
    // Sample XML file:
    // <?xml version="1.0" encoding="utf-8"?>
    // <KeyFile>
    //     <Meta>
    //         <Version>1.00</Version>
    //     </Meta>
    //     <Key>
    //         <Data>ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI=</Data>
    //     </Key>
    // </KeyFile>
    """
    with open(filename, 'r') as f:
        # ignore meta, currently there is only version "1.00"
        tree = ElementTree.parse(f).getroot()
        # read text from key, data and convert from base64
        return base64.b64decode(tree.find('Key/Data').text)
    # raise IOError('Could not parse XML keyfile.')


def load_plain_keyfile(filename):
    """
    A "plain" keyfile is a file containing only the key.
    Any other file (JPEG, MP3, ...) can also be used as keyfile.
    """
    with open(filename, 'rb') as f:
        key = f.read()
        # if the length is 32 bytes we assume it is the key
        if len(key) == 32:
            return key
        # if the length is 64 bytes we assume the key is hex encoded
        if len(key) == 64:
            return codecs.decode(key, 'hex')
        # anything else may be a file to hash for the key
        return sha256(key)
    # raise IOError('Could not read keyfile.')


def stream_unpack(stream, offset, length, typecode='I'):
    if offset is not None:
        stream.seek(offset)
    data = stream.read(length)
    return struct.unpack('<' + typecode, data)[0]


def read_signature(stream):
    sig1 = stream_unpack(stream, 0, 4)
    sig2 = stream_unpack(stream, None, 4)
    # ver_minor = stream_unpack(stream, None, 2, 'h')
    # ver_major = stream_unpack(stream, None, 2, 'h')
    # return (sig1, sig2, ver_major, ver_minor)
    return sig1, sig2