EventGhost/EventGhost

View on GitHub
eg/Classes/Password.py

Summary

Maintainability
A
0 mins
Test Coverage
# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright © 2005-2020 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost 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 EventGhost. If not, see <http://www.gnu.org/licenses/>.

import ctypes
import hashlib
import pickle
import struct
import weakref
import wx
from comtypes import GUID
from Crypto.Cipher import AES

# Local imports
import eg
from eg.WinApi.Dynamic import GetVolumeInformation, DWORD, byref

class MasterPasswordDialog(wx.Dialog):
    def __init__(self):
        self.result = None
        wx.Dialog.__init__(self, eg.document.frame)
        staticText = wx.StaticText(
            self, -1, "Please enter your master password:"
        )
        self.passwordCtrl = wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD)
        self.buttonRow = eg.ButtonRow(self, (wx.ID_OK, wx.ID_CANCEL))
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(staticText, 0, wx.EXPAND | wx.ALL, 5)
        sizer.Add(self.passwordCtrl, 0, wx.EXPAND | wx.ALL, 5)
        sizer.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
        sizer.Add(self.buttonRow.sizer, 0, wx.ALIGN_CENTER)
        self.SetSizerAndFit(sizer)

    def OnOK(self, event):
        self.result = self.passwordCtrl.GetValue()
        event.Skip()


class Password(object):
    database = {}
    instances = weakref.WeakKeyDictionary()
    masterkey = "EventGhost"

    def __init__(self, guid=None, content=None):
        if guid is None:
            guid = str(GUID.create_new())
        elif isinstance(guid, Password):
            guid = guid.guid
        if content is not None:
            self.database[guid] = content
        self.guid = guid

    def __new__(cls, *args, **kwargs):
        self = super(Password, cls).__new__(cls)
        cls.instances[self] = 1
        return self

    def __repr__(self):
        return "eg.Password(%r)" % self.guid

    def __str__(self):
        return self.Get()

    def __unicode__(self):
        return self.Get()

    def Get(self):
        try:
            return self.database[self.guid]
        except KeyError:
            return ""

    @classmethod
    def GetDatabaseContent(cls):
        newDatabase = {}
        for instance in cls.instances.keys():
            guid = instance.guid
            try:
                newDatabase[guid] = cls.database[guid]
            except KeyError:
                pass
        if len(newDatabase) == 0:
            return ""
        text = pickle.dumps(newDatabase)
        key = cls.masterkey
        # The length of the key must be a multiple of 16
        key = key + "X" * (16 - len(key) % 16)
        hashObj = hashlib.md5()
        hashObj.update(text)
        textHash = hashObj.digest()
        length = len(textHash + text) + 4
        padding = ("X" * (32 - length % 32))
        paddedString = textHash + struct.pack("L", length) + text + padding
        return AES.new(key, AES.MODE_ECB).encrypt(paddedString)

    def Set(self, password):
        self.database[self.guid] = password

    @classmethod
    def SetDatabaseContent(cls, data):
        if not data:
            cls.database = {}
            return
        key = cls.masterkey
        # The length of the key must be a multiple of 16
        key = key + "X" * (16 - (len(key) % 16))
        paddedString = AES.new(key, AES.MODE_ECB).decrypt(data)
        textHash = paddedString[:16]
        length = struct.unpack("L", paddedString[16:20])[0]
        plaintext = paddedString[20:length]
        hashObj = hashlib.md5()
        hashObj.update(plaintext)
        hash2 = hashObj.digest()
        if textHash != hash2:
            eg.PrintError("Decryption error.")
            cls.database = {}
            return
        cls.database = pickle.loads(plaintext)


def GetMachineKey():
    # Get the volume serial number of the system drive
    volumeSerialBuffer = DWORD()
    GetVolumeInformation(
        "C:\\", None, 0, byref(volumeSerialBuffer), None, None, None, 0
    )
    value = volumeSerialBuffer.value
    volumeSerial = (
        chr((value >> 24) & 0xFF) +
        chr((value >> 16) & 0xFF) +
        chr((value >> 8) & 0xFF) +
        chr(value & 0xFF)
    )
    # The last 6 bytes of the UUID returned from UuidCreateSequential contain
    # the hardware MAC address.
    uuid = ctypes.create_string_buffer(16)
    ctypes.windll.rpcrt4.UuidCreateSequential(uuid)
    mac = uuid.raw[-6:]
    return volumeSerial + mac

MACHINE_KEY = GetMachineKey()