From d1bd5224b225972cf50df38417d4b174bb0b9a06 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 23 Dec 2013 23:49:11 -0500 Subject: [PATCH] Added keyhook code, reworked things to use it. --- 1pass.c | 22 ++++++++ 1pass.lua | 119 +++++++++++++++++++++++++------------------ CMakeLists.txt | 2 + keyhook.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++ keyhook.h | 11 ++++ 5 files changed, 238 insertions(+), 50 deletions(-) create mode 100644 keyhook.c create mode 100644 keyhook.h diff --git a/1pass.c b/1pass.c index 60278a7..1104c07 100644 --- a/1pass.c +++ b/1pass.c @@ -9,6 +9,7 @@ #include "aes.h" #include "base64.h" #include "md5.h" +#include "keyhook.h" #include #define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) ) @@ -287,8 +288,29 @@ static int popupGuiMenu(lua_State *L) } // popupGuiMenu +static void keyhookPressed(void) +{ + lua_getglobal(luaState, "keyhookPressed"); + lua_call(luaState, 0, 0); +} // keyhookPressed + + +static gboolean keyhookPumper(void *arg) +{ + if (pumpKeyHook()) + keyhookPressed(); + return TRUE; // keep firing timer +} // keyhookPumper + + static int giveControlToGui(lua_State *L) { + if (initKeyHook()) + { + atexit(deinitKeyHook); + g_timeout_add(200, (GSourceFunc) keyhookPumper, (gpointer) NULL); + } // if + gtk_main(); return 0; } // giveControlToGui diff --git a/1pass.lua b/1pass.lua index 806bc92..d0b2a8b 100644 --- a/1pass.lua +++ b/1pass.lua @@ -1,6 +1,11 @@ JSON = (loadfile "JSON.lua")() dofile("dumptable.lua") +local basedir = "1Password/1Password.agilekeychain/data/default" -- !!! FIXME +local password = argv[2] +local items = nil +local keyhookRunning = false + local passwordTypeNameMap = { ["webforms.WebForm"] = "Logins", ["wallet.financial.CreditCard"] = "Credit cards", @@ -42,7 +47,7 @@ end local keys = {} -function loadKey(basedir, level, password) +local function loadKey(level, password) if keys[level] ~= nil then return keys[level] end @@ -78,7 +83,7 @@ function loadKey(basedir, level, password) return nil end -local function getHint(basedir) +local function getHint() local f = io.open(basedir .. "/.password.hint", "r") if (f == nil) then return @@ -91,7 +96,7 @@ local function getHint(basedir) end -function loadContents(basedir) +local function loadContents() return load_json(basedir .. "/contents.js") end @@ -109,6 +114,7 @@ local function build_secret_menuitem(menu, type, str, hidden) local callback = function() copyToClipboard(str) --print("Copied data [" .. str .. "] to clipboard.") + keyhookRunning = false end return appendGuiMenuItem(menu, text, callback) end @@ -208,13 +214,13 @@ end secret_menuitem_builders["wallet.financial.CreditCard"] = build_secret_menuitem_creditcard -local function build_secret_menuitems(basedir, info, menu, password) +local function build_secret_menuitems(info, menu) local metadata = load_json(basedir .. "/" .. info.uuid .. ".1password") if metadata == nil then return end - local plaintext = decryptBase64UsingKey(metadata.encrypted, loadKey(basedir, metadata.securityLevel, password)) + local plaintext = decryptBase64UsingKey(metadata.encrypted, loadKey(metadata.securityLevel, password)) if plaintext == nil then return end @@ -238,60 +244,73 @@ local function build_secret_menuitems(basedir, info, menu, password) setGuiMenuItemSubmenu(menuitem, submenu) end - --- Mainline! - ---for i,v in ipairs(argv) do --- print("argv[" .. i .. "] = " .. v) ---end - -local basedir = "1Password/1Password.agilekeychain/data/default" -- !!! FIXME - -local password = argv[2] -while password == nil do - password = runGuiPasswordPrompt(getHint(basedir)) - if password == nil then - os.exit(1) - end - if loadKey(basedir, "SL5", password) == nil then - password = nil -- wrong password - local start = os.time() -- cook the CPU for three seconds. - local now = start - while os.difftime(now, start) < 3 do - now = os.time() +local function prepItems() + items = {} + local contents = loadContents() + for i,v in ipairs(contents) do + local t = v[2] + if items[t] == nil then + items[t] = {} end + local bucket = items[t] + bucket[#bucket+1] = { uuid=v[1], type=t, name=v[3], url=v[4] } -- !!! FIXME: there are more fields, don't know what they mean yet. end end -local contents = loadContents(basedir) -local items = {} -for i,v in ipairs(contents) do - local t = v[2] - if items[t] == nil then - items[t] = {} +function keyhookPressed() -- not local! Called from C! + if keyhookRunning then + return end - local bucket = items[t] - bucket[#bucket+1] = { uuid=v[1], type=t, name=v[3], url=v[4] } -- !!! FIXME: there are more fields, don't know what they mean yet. -end -contents = nil - -local topmenu = makeGuiMenu() -for orderi,type in ipairs(passwordTypeOrdering) do - local bucket = items[type] - local realname = passwordTypeNameMap[type] - if realname == nil then - realname = type + + keyhookRunning = true + + while password == nil do + password = runGuiPasswordPrompt(getHint()) + if password == nil then + keyhookRunning = false + return + end + if loadKey("SL5", password) == nil then + password = nil -- wrong password + local start = os.time() -- cook the CPU for three seconds. + local now = start + while os.difftime(now, start) < 3 do + now = os.time() + end + end end - local menuitem = appendGuiMenuItem(topmenu, realname) - local submenu = makeGuiMenu() - table.sort(bucket, function(a, b) return a.name < b.name end) - for i,v in pairs(bucket) do - build_secret_menuitems(basedir, v, submenu, password) + + prepItems() + + local topmenu = makeGuiMenu() + for orderi,type in ipairs(passwordTypeOrdering) do + local bucket = items[type] + local realname = passwordTypeNameMap[type] + if realname == nil then + realname = type + end + local menuitem = appendGuiMenuItem(topmenu, realname) + local submenu = makeGuiMenu() + table.sort(bucket, function(a, b) return a.name < b.name end) + for i,v in pairs(bucket) do + build_secret_menuitems(v, submenu) + end + setGuiMenuItemSubmenu(menuitem, submenu) end - setGuiMenuItemSubmenu(menuitem, submenu) + + popupGuiMenu(topmenu) end -popupGuiMenu(topmenu) + +-- Mainline! + +--for i,v in ipairs(argv) do +-- print("argv[" .. i .. "] = " .. v) +--end + +-- !!! FIXME: message box, exit if basedir is wack. +-- !!! FIXME: this can probably happen in C now (the Lua mainline is basically gone now). +--print("Now waiting for keyhook.") giveControlToGui() -- end of 1pass.lua ... diff --git a/CMakeLists.txt b/CMakeLists.txt index c363081..4b223b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ include_directories("lua") add_executable(1pass 1pass.c + keyhook.c pkcs5_pbkdf2.c aes.c md5.c @@ -121,6 +122,7 @@ target_link_libraries(1pass ${PKG_GTKPLUS2_LIBRARIES}) if(LINUX) set_target_properties(1pass PROPERTIES LINK_FLAGS "-Wl,-rpath,$ORIGIN") target_link_libraries(1pass "m") + target_link_libraries(1pass "Xtst") endif() # end of CMakeLists.txt ... diff --git a/keyhook.c b/keyhook.c new file mode 100644 index 0000000..4e1d6a7 --- /dev/null +++ b/keyhook.c @@ -0,0 +1,134 @@ +// !!! FIXME: this is X11 specific. :( + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "keyhook.h" + +static volatile int keyPressFlags = 0; +static volatile int sawKeyCombo = 0; +static void keyhookCallback(XPointer priv, XRecordInterceptData *data) +{ + const xEvent *xev = (const xEvent *) data->data; + if (data->category == XRecordFromServer) + { + const BYTE keycode = xev->u.u.detail; + if (xev->u.u.type == KeyPress) + { + // !!! FIXME: don't hardcode these keycodes. + if ((keycode == 64) && (keyPressFlags == 0)) + keyPressFlags++; + else if ((keycode == 133) && (keyPressFlags == 1)) + keyPressFlags++; + else if ((keycode == 51) && (keyPressFlags == 2)) + { + sawKeyCombo = 1; + keyPressFlags = 0; + } // else if + else + keyPressFlags = 0; + } // if + else if (xev->u.u.type == KeyRelease) + { + keyPressFlags = 0; + } // else if + } // if + + XRecordFreeData(data); +} // keyhookCallback + + +// every example I've seen needs two Display connections...one for the +// keyhook, and one to control it. +static Display *ctrldpy = NULL; +static Display *datadpy = NULL; +static XRecordContext xrc = 0; + +int initKeyHook(void) +{ + int major = 0; + int minor = 0; + XRecordRange *xrr = NULL; + XRecordClientSpec xrcs = XRecordAllClients; + + if (ctrldpy) + return 0; // already initialized. + + ctrldpy = XOpenDisplay(NULL); + if (!ctrldpy) + goto failed; + + XSynchronize(ctrldpy, True); + + datadpy = XOpenDisplay(NULL); + if (!datadpy) + goto failed; + else if (!XRecordQueryVersion(ctrldpy, &major, &minor)) + goto failed; + else if ((xrr = XRecordAllocRange()) == NULL) + goto failed; + + memset(xrr, '\0', sizeof (*xrr)); + xrr->device_events.first = KeyPress; + xrr->device_events.last = KeyPress; + + if ((xrc = XRecordCreateContext(ctrldpy, 0, &xrcs, 1, &xrr, 1)) == 0) + goto failed; + else if (!XRecordEnableContextAsync(datadpy, xrc, keyhookCallback, NULL)) + goto failed; + + XFree(xrr); + xrr = NULL; + + return 1; + +failed: + deinitKeyHook(); + if (xrr) XFree(xrr); + + return 0; +} // initKeyHook + + +void deinitKeyHook(void) +{ + if (ctrldpy) + { + if (xrc) + { + XRecordDisableContext(ctrldpy, xrc); + XRecordFreeContext(ctrldpy, xrc); + } // if + XCloseDisplay(ctrldpy); + } // if + + if (datadpy) + XCloseDisplay(datadpy); + + ctrldpy = NULL; + datadpy = NULL; + xrc = 0; + sawKeyCombo = 0; + keyPressFlags = 0; +} // deinitKeyHook + + +int pumpKeyHook(void) +{ + if (!datadpy) + return 0; + + sawKeyCombo = 0; + XRecordProcessReplies(datadpy); + return sawKeyCombo; +} // pumpKeyHook + +// end of keyhook.c ... + diff --git a/keyhook.h b/keyhook.h new file mode 100644 index 0000000..803546d --- /dev/null +++ b/keyhook.h @@ -0,0 +1,11 @@ +#ifndef _INCL_KEYHOOK_H_ +#define _INCL_KEYHOOK_H_ + +int pumpKeyHook(void); +void deinitKeyHook(void); +int initKeyHook(void); + +#endif + +// end of keyhook.h ... +