commit cf1577319e0bd375c0acd47dd609a4b2c68a6185
Author: dsorber <david.sorber@gmail.com>
Date:   Sat Sep 16 12:22:53 2017 -0400

    pwmgr v0.3.0 -- Improved security of encryption/decryption operations, bound ENTER to dialog OK buttons to improve ease of use, and implemented key bindings for common operations.

diff --git a/software/pwmgr/pwmgr.py b/software/pwmgr/pwmgr.py
index 0b552c7..71964d7 100755
--- a/software/pwmgr/pwmgr.py
+++ b/software/pwmgr/pwmgr.py
@@ -1,4 +1,5 @@
 #!/usr/bin/python3
+import glob
 import os
 import shutil
 import subprocess
@@ -6,7 +7,7 @@ import sys
 
 import gi
 gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Pango
+from gi.repository import Gtk, Pango, Gdk
 
 MOUNT_PATH = '/mnt/pw_ramdisk'
 PWD_FILE_PATH = '/home/dsorber/Desktop/network'
@@ -14,8 +15,9 @@ PWD_FILE_PATH = '/home/dsorber/Desktop/network'
 ENC_FILE_PATH = '/home/dsorber/Documents/chicken_soup.enc'
 SCRATCH_FILE_PATH = '/mnt/pw_ramdisk/chicken_soup.txt'
 ENC_TMP_FILE_PATH = '/home/dsorber/Documents/chicken_soup.enc.tmp'
+KEY_FILE_PATH = '/mnt/pw_ramdisk/.f05d11ef-000c-45c2-ae14-56b56c23e1c5'
 
-VERSION = '0.0.2'
+VERSION = '0.0.3'
 
 class SearchDialog(Gtk.Dialog):
 
@@ -31,8 +33,14 @@ class SearchDialog(Gtk.Dialog):
         box.add(label)
 
         self.entry = Gtk.Entry()
+        self.entry.set_activates_default(True)
         box.add(self.entry)
 
+        # Make hitting ENTER trigger the OK button
+        ok_button = self.get_widget_for_response(response_id=Gtk.ResponseType.OK)
+        ok_button.set_can_default(True)
+        ok_button.grab_default()
+
         self.show_all()
         
         
@@ -61,7 +69,7 @@ def get_user_pw(parent, message, title=''):
                                Gtk.MessageType.QUESTION,
                                Gtk.ButtonsType.OK_CANCEL,
                                message)
-
+    
     dialog.set_title(title)
 
     box = dialog.get_content_area()
@@ -69,9 +77,15 @@ def get_user_pw(parent, message, title=''):
     pw_entry.set_visibility(False)
     pw_entry.set_invisible_char('*')
     pw_entry.set_size_request(200, 0)
+    pw_entry.set_activates_default(True)
     
     box.add(pw_entry)
     #~ box.pack_end(pw_entry, False, False, 0)
+    
+    # Make hitting ENTER trigger the OK button
+    ok_button = dialog.get_widget_for_response(response_id=Gtk.ResponseType.OK)
+    ok_button.set_can_default(True)
+    ok_button.grab_default()
 
     dialog.show_all()
     response = dialog.run()
@@ -96,10 +110,33 @@ class TextViewWindow(Gtk.Window):
         self.create_textview()
         self.create_toolbar()
         self.create_bottom()
+        self.connect("key-press-event", self._key_press_event)        
+        
         
         self.mounted = False
         self.pwd_file_fd = None
-        self.pwd = None
+        
+        
+
+    def _key_press_event(self, widget, event):
+        keyval = event.keyval
+        keyval_name = Gdk.keyval_name(keyval)
+        state = event.state
+        
+        ctrl = (state & Gdk.ModifierType.CONTROL_MASK)
+        
+        # Bind existing handlers to keys
+        if ctrl and keyval_name == 'f':
+            self.on_search_clicked(widget)
+        elif ctrl and keyval_name == 'o':
+            self.open_pwd_file(widget)
+        elif ctrl and keyval_name == 's':
+            self.save_pwd_file(widget)
+        elif ctrl and keyval_name == 'q':
+            self.close_pwd_file(widget, None)
+        else:
+            return False
+        return True
 
     def create_toolbar(self):
         toolbar = Gtk.Toolbar()
@@ -168,14 +205,12 @@ class TextViewWindow(Gtk.Window):
         """ 
         Open handler
         """
-        
+
         # Get password
-        self.pwd = get_user_pw(self, 'Please enter your password', 'Password')
-        if self.pwd is None:
+        passwd = get_user_pw(self, 'Please enter your password', 'Password')
+        if passwd is None:
             return
         
-        #~ print('User entered: {:s}'.format(self.pwd))
-        
         if not self.mounted:
             os.makedirs(MOUNT_PATH, 0o600, exist_ok=True)
             
@@ -185,8 +220,14 @@ class TextViewWindow(Gtk.Window):
             subprocess.call(mnt_cmd, shell=True, timeout=5)
             self.mounted = True
             
-            cmd = 'openssl enc -aes-256-cbc -d -in {:s} -out {:s} -pass pass:{:s}'
-            cmd = cmd.format(ENC_FILE_PATH, SCRATCH_FILE_PATH, self.pwd)
+            # Store password
+            with open(KEY_FILE_PATH, 'w') as pass_wd_file:
+                pass_wd_file.write(passwd)
+            os.chmod(KEY_FILE_PATH, 0o600)
+            
+            # Decrypt password store
+            cmd = 'openssl enc -aes-256-cbc -d -in {:s} -out {:s} -pass file:{:s}'
+            cmd = cmd.format(ENC_FILE_PATH, SCRATCH_FILE_PATH, KEY_FILE_PATH)
             subprocess.call(cmd, shell=True, timeout=5)
             
             # Open and load file; make textview editable
@@ -202,6 +243,7 @@ class TextViewWindow(Gtk.Window):
         
         if self.mounted:
             
+            # Write the password store datat to the sratch file
             with open(SCRATCH_FILE_PATH, 'w') as pwd_file_fd:
                 pwd_file_fd.write(
                     self.textbuffer.get_text(self.textbuffer.get_start_iter(),
@@ -209,16 +251,28 @@ class TextViewWindow(Gtk.Window):
                                              True))
             print('Write complete')
             
-            cmd = 'openssl enc -aes-256-cbc -salt -in {:s} -out {:s} -pass pass:{:s}'
-            cmd = cmd.format(SCRATCH_FILE_PATH, ENC_TMP_FILE_PATH, self.pwd)
+            # Encrypt the password store
+            cmd = 'openssl enc -aes-256-cbc -salt -in {:s} -out {:s} -pass file:{:s}'
+            cmd = cmd.format(SCRATCH_FILE_PATH, ENC_TMP_FILE_PATH, KEY_FILE_PATH)
             subprocess.call(cmd, shell=True, timeout=5)
-            print('enc complete')
+            print('Enc complete')
+            
+            # Remove the key file, then copy over the encrypted file with the
+            # just created temp one
+            os.remove(KEY_FILE_PATH)
+            os.remove(ENC_FILE_PATH)
+            os.rename(ENC_TMP_FILE_PATH, ENC_FILE_PATH)
             
             # Clear the buffer and disable the textview
             self.textbuffer.set_text('')
             self.textview.set_editable(False)
             self.textview.set_cursor_visible(False)
-            
+    
+    def _remove_mount_files(self):
+        # Remove files in the mount path
+        for file_ in glob.glob('{:s}/*'.format(MOUNT_PATH)):
+            os.remove(file_)
+    
     def close_pwd_file(self, widget, other):
         """
         Close handler
@@ -228,6 +282,11 @@ class TextViewWindow(Gtk.Window):
         #~ response = dialog.run()
         #~ dialog.destroy()
 
+        # Clear the buffer and disable the textview
+        self.textbuffer.set_text('')
+        self.textview.set_editable(False)
+        self.textview.set_cursor_visible(False)
+
         rand_write_cmd = 'dd if=/dev/urandom of={:s} bs=16M count=1 status=none'
         rand_write_cmd = rand_write_cmd.format(os.path.join(MOUNT_PATH, 'data'))
         
@@ -235,17 +294,15 @@ class TextViewWindow(Gtk.Window):
         zero_write_cmd = zero_write_cmd.format(os.path.join(MOUNT_PATH, 'data'))
         
         if self.mounted:
-            #~ shutil.rmtree(os.path.join(MOUNT_PATH, '*'), ignore_errors=True)
-            rm_cmd = 'rm -rf {:s}'.format(os.path.join(MOUNT_PATH, '*'))
-            subprocess.call(rm_cmd, shell=True, timeout=5)
+            self._remove_mount_files()
             
             # Overwrite a few times
             for ctr in range(10):
                 print(ctr)
                 subprocess.call(rand_write_cmd, shell=True, timeout=5)
-                subprocess.call(rm_cmd, shell=True, timeout=5)
+                self._remove_mount_files()
                 subprocess.call(zero_write_cmd, shell=True, timeout=5)
-                subprocess.call(rm_cmd, shell=True, timeout=5)
+                self._remove_mount_files()
             
             umount_cmd = 'umount {:s}'.format(MOUNT_PATH)
             subprocess.call(umount_cmd, shell=True, timeout=5)
@@ -282,7 +339,7 @@ class TextViewWindow(Gtk.Window):
         match = start.forward_search(text, 0, end)
 
         if match != None:
-            match_start, match_end = match            
+            match_start, match_end = match
             self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
             
             # Scroll so the match appears in the middle of the screen
