This file is indexed.

/usr/share/pyshared/pwman/util/crypto.py is in pwman3 0.4.2-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#============================================================================
# This file is part of Pwman3.
#
# Pwman3 is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2
# as published by the Free Software Foundation;
#
# Pwman3 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pwman3; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#============================================================================
# Copyright (C) 2012 Oz Nahum <nahumoz@gmail.com>
#============================================================================
# Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
#============================================================================

"""
Encryption Module used by PwmanDatabase

Supports AES, ARC2, Blowfish, CAST, DES, DES3, IDEA, RC5.

Usage:
import pwman.util.crypto.CryptoEngine as CryptoEngine
from pwman.util.crypto import CryptoEngine

class myCallback(CryptoEngine.Callback):
    def execute(self):
        return "mykey"

params = {'encryptionAlgorithm': 'AES',
          'encryptionCallback': callbackFunction}

CryptoEngine.init(params)

crypto = CryptoEngine.get()
ciphertext = crypto.encrypt("plaintext")
plaintext = cyypto.decrypt(ciphertext)
"""

from Crypto.Cipher import Blowfish as cBlowfish
from Crypto.Cipher import AES as cAES
from Crypto.Cipher import ARC2 as cARC2
from Crypto.Cipher import ARC4 as cARC4
from Crypto.Cipher import CAST as cCAST
from Crypto.Cipher import DES as cDES
from Crypto.Cipher import DES3 as cDES3

from Crypto.Random import OSRNG


from pwman.util.callback import Callback
import pwman.util.config as config
import cPickle
import time
import sys
import ctypes


def zerome(string):
    """
    securely erase strings ...
    for windows: ctypes.cdll.msvcrt.memset
    """
    bufsize = len(string) + 1
    offset = sys.getsizeof(string) - bufsize
    ctypes.memset(id(string) + offset, 0, bufsize)

# Use this to tell if crypto is successful or not
_TAG = "PWMANCRYPTO"

_INSTANCE = None

# Use this to tell if crypto is successful or not
_TAG = "PWMANCRYPTO"


class CryptoException(Exception):
    """Generic Crypto Exception."""
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return "CryptoException: " + self.message


class CryptoUnsupportedException(CryptoException):
    """Unsupported feature requested."""
    def __str__(self):
        return "CryptoUnsupportedException: " + self.message


class CryptoBadKeyException(CryptoException):
    """Encryption key is incorrect."""
    def __str__(self):
        return "CryptoBadKeyException: " + self.message


class CryptoNoKeyException(CryptoException):
    """No key has been initalised."""
    def __str__(self):
        return "CryptoNoKeyException: " + self.message


class CryptoNoCallbackException(CryptoException):
    """No Callback has been set."""
    def __str__(self):
        return "CryptoNoCallbackException: " + self.message


class CryptoPasswordMismatchException(CryptoException):
    """Entered passwords do not match."""
    def __str__(self):
        return "CryptoPasswordMismatchException: " + self.message


class CryptoEngine(object):
    """Cryptographic Engine"""
    _timeoutcount = 0
    _instance = None
    _callback = None

    @classmethod
    def get(cls):
        """
        CryptoEngine.get() -> CryptoEngine
        Return an instance of CryptoEngine.
        If no instance is found, a CryptoException is raised.
        """
        if CryptoEngine._instance is None:
            algo = config.get_value("Encryption", "algorithm")
            if algo == "Dummy":
                CryptoEngine._instance = DummyCryptoEngine()
            else:
                CryptoEngine._instance = CryptoEngine()
        return CryptoEngine._instance
    # get = classmethod(get)

    def __init__(self):
        """Initialise the Cryptographic Engine

        params is a dictionary. Valid keys are:
        algorithm: Which cipher to use
        callback:  Callback class.
        keycrypted: This should be set by the database layer.
        timeout:   Time after which key will be forgotten.
                             Default is -1 (disabled).
        """
        algo = config.get_value("Encryption", "algorithm")
        if algo:
            self._algo = algo
        else:
            raise CryptoException("Parameters missing, no algorithm given")

        callback = config.get_value("Encryption", "callback")
        if isinstance(callback, Callback):
            self._callback = callback
        else:
            self._callback = None

        keycrypted = config.get_value("Encryption", "keycrypted")
        if len(keycrypted) > 0:
            self._keycrypted = keycrypted
        else:
            self._keycrypted = None

        timeout = config.get_value("Encryption", "timeout")
        if timeout.isdigit():
            self._timeout = timeout
        else:
            self._timeout = -1
        self._cipher = None

    def encrypt(self, obj):
        """
        encrypt(obj) -> ciphertext
        Encrypt obj and return its ciphertext. obj must be a picklable class.
        Can raise a CryptoException and CryptoUnsupportedException"""
        cipher = self._getcipher()
        plaintext = self._preparedata(obj, cipher.block_size)
        ciphertext = cipher.encrypt(plaintext)

        return str(ciphertext).encode('base64')

    def decrypt(self, ciphertext):
        """
        decrypt(ciphertext) -> obj
        Decrypt ciphertext and returns the obj that was encrypted.
        If key is bad, a CryptoBadKeyException is raised
        Can also raise a CryptoException and CryptoUnsupportedException"""
        cipher = self._getcipher()
        ciphertext = str(ciphertext).decode('base64')
        plaintext = cipher.decrypt(ciphertext)
        return self._retrievedata(plaintext)

    def set_cryptedkey(self, key):
        """
        hold _keycrypted
        """
        self._keycrypted = key

    def get_cryptedkey(self):
        """
        return _keycrypted
        """
        return self._keycrypted

    def set_callback(self, callback):
        """
        set the callback function
        """
        self._callback = callback

    def get_callback(self):
        """
        return call back function
        """
        return self._callback

    def changepassword(self):
        """
        Creates a new key. The key itself is actually stored in
        the database in crypted form. This key is encrypted using the
        password that the user provides. This makes it easy to change the
        password for the database.
        If oldKeyCrypted is none, then a new password is generated."""
        if self._callback is None:
            raise CryptoNoCallbackException("No call back class has been \
specified")
        if self._keycrypted is None:
            # Generate a new key, 32 bits in length, if that's
            # too long for the Cipher, _getCipherReal will sort it out
            random = OSRNG.new()
            key = str(random.read(32)).encode('base64')
        else:
            password = self._callback.getsecret("Please enter your current \
password")
            cipher = self._getcipher_real(password, self._algo)
            plainkey = cipher.decrypt(str(self._keycrypted).decode('base64'))
            key = self._retrievedata(plainkey)

        newpassword1 = self._callback.getsecret("Please enter your new \
password")
        newpassword2 = self._callback.getsecret("Please enter your new \
password again")
        while newpassword1 != newpassword2:
            print "Passwords do not match!"
            newpassword1 = self._callback.getsecret("Please enter your new \
password")
            newpassword2 = self._callback.getsecret("Please enter your new \
password again")

        # if (newpassword1 != newpassword2):
        #   raise CryptoPasswordMismatchException("Passwords do not match")
        newcipher = self._getcipher_real(newpassword1, self._algo)
        self._keycrypted = str(newcipher.encrypt(
                               self._preparedata(key,
                                                 newcipher.block_size)
                               )).encode('base64')
        # newpassword1, newpassword2 are not needed any more so we erase
        # them
        zerome(newpassword1)
        zerome(newpassword2)
        # we also want to create the cipher if there isn't one already
        # so this CryptoEngine can be used from now on
        if self._cipher is None:
            self._cipher = self._getcipher_real(str(key).decode('base64'),
                                                self._algo)
            CryptoEngine._timeoutcount = time.time()

        return self._keycrypted

    def alive(self):
        """
        check if we have cipher
        """
        if self._cipher is not None:
            return True
        else:
            return False

    def forget(self):
        """
        discard cipher
        """
        self._cipher = None

    def _getcipher(self):
        """
        get cypher from user, to decrypt DB
        """
        if (self._cipher is not None
            and (self._timeout == -1
                 or (time.time() -
                     CryptoEngine._timeoutcount) < self._timeout)):
            return self._cipher
        if self._callback is None:
            raise CryptoNoCallbackException("No Callback exception")
        if self._keycrypted is None:
            raise CryptoNoKeyException("Encryption key has not been generated")

        max_tries = 5
        tries = 0

        key = None

        while tries < max_tries:
            try:
                password = self._callback.getsecret("Please enter your \
password")
                tmpcipher = self._getcipher_real(password, self._algo)
                plainkey = tmpcipher.decrypt(str(self._keycrypted).decode(
                    'base64'))
                key = self._retrievedata(plainkey)
                break
            except CryptoBadKeyException:
                print "Wrong password."
                tries += 1

        if not key:
            raise Exception("Wrong password entered %s times; giving up"
                            % max_tries)
        try:
            key = str(key).decode('base64')
        except Exception:
            key = cPickle.loads(key)
            key = str(key).decode('base64')
        self._cipher = self._getcipher_real(key,
                                            self._algo)

        CryptoEngine._timeoutcount = time.time()
        return self._cipher

    def _getcipher_real(self, key, algo):
        """
        do the real job of decrypting using functions
        form PyCrypto
        """
        if (algo == "AES"):
            key = self._padkey(key, [16, 24, 32])
            cipher = cAES.new(key, cAES.MODE_ECB)
        elif (algo == 'ARC2'):
            cipher = cARC2.new(key, cARC2.MODE_ECB)
        elif (algo == 'ARC4'):
            raise CryptoUnsupportedException("ARC4 is currently unsupported")
        elif (algo == 'Blowfish'):
            cipher = cBlowfish.new(key, cBlowfish.MODE_ECB)
        elif (algo == 'CAST'):
            cipher = cCAST.new(key, cCAST.MODE_ECB)
        elif (algo == 'DES'):
            self._padkey(key, [8])
            cipher = cDES.new(key, cDES.MODE_ECB)
        elif (algo == 'DES3'):
            key = self._padkey(key, [16, 24])
            cipher = cDES3.new(key, cDES3.MODE_ECB)
        elif (algo == 'XOR'):
            raise CryptoUnsupportedException("XOR is currently unsupported")
        else:
            raise CryptoException("Invalid algorithm specified")
        return cipher

    def _padkey(self, key, acceptable_lengths):
        """
        pad key with extra string
        """
        maxlen = max(acceptable_lengths)
        keylen = len(key)
        if (keylen > maxlen):
            return key[0:maxlen]
        acceptable_lengths.sort()
        acceptable_lengths.reverse()
        newkeylen = None
        for i in acceptable_lengths:
            if (i < keylen):
                break
            newkeylen = i
        return key.ljust(newkeylen)

    def _preparedata(self, obj, blocksize):
        """
        prepare data before encrypting
        """
        #plaintext = cPickle.dumps(obj)
        plaintext = _TAG + obj
        numblocks = (len(plaintext)/blocksize) + 1
        newdatasize = blocksize*numblocks
        return plaintext.ljust(newdatasize)

    def _retrievedata(self, plaintext):
        """
        retrieve encrypted data
        """
        if (plaintext.startswith(_TAG)):
            plaintext = plaintext[len(_TAG):]
        else:
            raise CryptoBadKeyException("Error decrypting, bad key")
        try:
            # old db version used to write stuff to db with
            # plaintext = cPickle.dumps(obj)
            # TODO: completely remove this block, and convert
            # the DB to a completely plain text ...
            return cPickle.loads(plaintext)
        except (TypeError, cPickle.UnpicklingError):
            return plaintext


class DummyCryptoEngine(CryptoEngine):
    """Dummy CryptoEngine used when database doesn't ask for encryption.
    Only for testing and debugging the DB drivers really."""
    def __init__(self):
        pass

    def encrypt(self, obj):
        """Return the object pickled."""
        return cPickle.dumps(obj)

    def decrypt(self, ciphertext):
        """Unpickle the object."""
        return cPickle.loads(str(ciphertext))

    def changepassword(self):
        return ''