1pass.lua
author Ryan C. Gordon <icculus@icculus.org>
Sun, 22 Dec 2013 03:01:08 -0500
changeset 11 b52e0f1798b8
parent 9 98c872cc0560
child 12 6a2d5b34d5ca
permissions -rw-r--r--
Start building in GUI stuff.

JSON = (loadfile "JSON.lua")()

local function load_json_str(str, desc)
    local retval = JSON:decode(str)
    return retval
end

local function load_json(fname)
    local f = io.open(fname, "rb")
    if (f == nil) then
        return nil
    end

    local str = f:read("*all")
    f:close()

    return load_json_str(str, fname)
end


local keys = {}
function loadKey(basedir, level, password)
    if keys[level] ~= nil then
        return keys[level]
    end

    local keysjson = load_json(basedir .. "/encryptionKeys.js");
    if (keysjson == nil) or (keysjson[level] == nil) then
        return nil
    end

    local identifier = keysjson[level]
    for i,v in ipairs(keysjson.list) do
        if v.identifier == identifier then
			local iterations = v.iterations
            if (iterations == nil) or (iterations < 1000) then
			    iterations = 1000
            end

			local decrypted = decryptUsingPBKDF2(v.data, password, iterations)
			if decrypted == nil then
                return nil
            end

			local validate = decryptBase64UsingKey(v.validation, decrypted)
			if validate ~= decrypted then
                return nil
            end

            keys[level] = decrypted
            return decrypted
        end
    end

    return nil
end

local function showHint(basedir)
    local f = io.open(basedir .. "/.password.hint", "r")
    if (f == nil) then
        return
    end

    local str = f:read("*all")
    f:close()

    print("(hint is '" .. str .. "').")
end


function loadContents(basedir)
    return load_json(basedir .. "/contents.js");
end

local function shouldFilterOut(filter, type, name, url)
    if filter == nil then
        return false   -- no filter? Don't filter.
    elseif type == "system.Tombstone" then
        return true    -- I guess those are dead items?
    elseif string.find(string.lower(name), filter) ~= nil then
        return false   -- matched keep-filter on name
    elseif string.find(string.lower(url), filter) ~= nil then
        return false   -- matched keep-filter on URL
    end
    return true  -- didn't match our keep-filter. Chuck it.
end


-- Mainline!

--for i,v in ipairs(argv) do
--    print("argv[" .. i .. "] = " .. v)
--end

local basedir = "1Password/1Password.agilekeychain/data/default"  -- !!! FIXME

local passwordTypeNameMap = {
    ["wallet.financial.BankAccountUS"] = "Bank accounts",
    ["wallet.financial.CreditCard"] = "Credit cards",
    ["webforms.WebForm"] = "Logins",
    ["system.Tombstone"] = "Dead items",
    ["wallet.membership.Membership"] = "Memberships",
    ["wallet.government.DriversLicense"] = "Drivers licenses",
    ["passwords.Password"] = "Passwords",
    -- !!! FIXME: more!
}

local contents = loadContents(basedir)
local items = {}
for i,v in ipairs(contents) do
    local t = v[2]
    if t ~= "system.Tombstone" then
        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
contents = nil

local topmenu = makeGuiMenu()
for type,bucket in pairs(items) do
print(type)
    local realname = passwordTypeNameMap[type]
    if realname == nil then
        realname = type
    end
    local menuitem = appendGuiMenuItem(topmenu, realname)
    local submenu = makeGuiMenu()
    for i,v in pairs(bucket) do
        local submenuitem = appendGuiMenuItem(submenu, v.name, function() print("Clicked on " .. v.name .. ", uuid is '" .. v.uuid .. "'") end)
    end
    setGuiMenuItemSubmenu(menuitem, submenu)
end

popupGuiMenu(topmenu)
giveControlToGui()

os.exit(1)


local password = argv[3]
if password == nil then
    showHint(basedir)
    io.write("password: ")
    password = io.read("*l")
end

if loadKey(basedir, "SL5", password) == nil then
    print("wrong password?\n")
    os.exit(1)
end

local filter = argv[2]
if filter ~= nil then
    filter = string.lower(filter)
end

items = loadContents(basedir)
for i,v in ipairs(items) do
    local type = v[2]
    local name = v[3]
    local url = v[4]
    if not shouldFilterOut(filter, type, name, url) then
        local metadata = load_json(basedir .. "/" .. v[1] .. ".1password")
        if metadata ~= nil then
            local plaintext = decryptBase64UsingKey(metadata.encrypted, loadKey(basedir, metadata.securityLevel, password))
            local username = nil
            local password = nil
            if plaintext ~= nil then
                local secure = load_json_str(plaintext, v[1])
                if type == "webforms.WebForm" then
                    for ii,vv in ipairs(secure.fields) do
                        if vv.type == "P" then
                            password = vv.value
                        elseif vv.type == "E" then
                            username = vv.value
                        end
                    end
                elseif type == "passwords.Password" then
                    password = secure.password
                end
            end

            print("item: " .. metadata.title)
            if username ~= nil then print("username: " .. username) end
            if password ~= nil then print("password: " .. password) end

        end
    end
end

-- end of 1pass.lua ...