Navigation Menu

Skip to content

Commit

Permalink
Initial work.
Browse files Browse the repository at this point in the history
Goes far enough to decrypt and correctly verify the master key.
  • Loading branch information
icculus committed Dec 18, 2013
0 parents commit 93a2c66
Show file tree
Hide file tree
Showing 78 changed files with 23,301 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .hgignore
@@ -0,0 +1,3 @@
syntax:glob
cmake-build
1Password
323 changes: 323 additions & 0 deletions 1pass.c
@@ -0,0 +1,323 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "pkcs5_pbkdf2.h"
#include "aes.h"
#include "base64.h"
#include "md5.h"

#define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) )

static lua_State *luaState = NULL;
static const uint8_t zero16[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
static const char saltprefix[] = { 'S', 'a', 'l', 't', 'e', 'd', '_', '_' };


static inline int retvalString(lua_State *L, const char *str)
{
if (str != NULL)
lua_pushstring(L, str);
else
lua_pushnil(L);
return 1;
} // retvalString


static inline int retvalStringBytes(lua_State *L, const uint8_t *str, size_t len)
{
//size_t i; printf("{\n"); for (i = 0; i < len; i++) { printf(" 0x%X\n", (unsigned int) str[i]); } printf(" }\n\n");

if (str != NULL)
lua_pushlstring(L, (const char *) str, len);
else
lua_pushnil(L);
return 1;
} // retvalStringBytes

static inline void xorBlock(uint8_t *dst, const uint8_t *src)
{
int i;
for (i = 0; i < 16; i++, dst++, src++)
*dst ^= *src;
} // xorBlock

static int decryptUsingKeyAndIvec(uint8_t *data, size_t *datalen, const uint8_t *key, const uint8_t *iv)
{
const size_t blocks = *datalen / 16;
uint8_t *block = data + ((blocks-1) * 16); // start at final block, work backwards
const uint8_t *padding = &block[15];
uint8_t expkey[aesExpandedKeySize];
size_t i;

if (blocks == 0)
return 1; // nothing to do.

aesExpandKey(key, expkey);

for (i = 0; i < blocks-1; i++)
{
aesDecrypt(block, expkey, block); // decrypt in place.
xorBlock(block, block-16);
block -= 16;
}
aesDecrypt(block, expkey, block); // decrypt in place.
xorBlock(block, iv); // xor against initial vector for final block.

if (*padding > 16)
return 0; // bad data?

*datalen -= *padding;

return 1;
} // decryptBinaryUsingKeyAndIvec


static inline int isSalted(const uint8_t *data, const size_t datalen)
{
return ( (datalen > sizeof (saltprefix)) &&
(memcmp(data, saltprefix, sizeof (saltprefix)) == 0) );
} // isSalted


static int decryptUsingPBKDF2(lua_State *L)
{
const char *base64 = luaL_checkstring(L, 1);
const char *password = luaL_checkstring(L, 2);
const int iterations = luaL_checkinteger(L, 3);
size_t datalen = strlen(base64);
uint8_t *dataptr = (uint8_t *) malloc(datalen);
uint8_t *data = dataptr;
base64_decodestate base64state;

base64_init_decodestate(&base64state);
datalen = base64_decode_block(base64, (int) datalen, data, &base64state);

const uint8_t *salt = zero16;
int saltlen = sizeof (zero16);
if (isSalted(data, datalen))
{
salt = data + 8;
saltlen = 8;
data += 16;
datalen -= 16;
} // if

uint8_t output[32];
pkcs5_pbkdf2(password, strlen(password), salt, saltlen, output, sizeof (output), (unsigned int) iterations);

const uint8_t *aeskey = &output[0];
const uint8_t *aesiv = &output[16];
if (decryptUsingKeyAndIvec(data, &datalen, aeskey, aesiv))
retvalStringBytes(L, data, datalen);
else
lua_pushnil(L);

free(dataptr);
return 1;
} // decryptUsingPBKDF2


static int decryptBase64UsingKey(lua_State *L)
{
size_t keylen = 0;
const char *base64 = luaL_checkstring(L, 1);
const uint8_t *key = (const uint8_t *) luaL_checklstring(L, 2, &keylen);
size_t datalen = strlen(base64);
uint8_t *dataptr = (uint8_t *) malloc(datalen);
uint8_t *data = dataptr;
base64_decodestate base64state;

base64_init_decodestate(&base64state);
datalen = base64_decode_block(base64, (int) datalen, data, &base64state);

uint8_t aeskey[16];
uint8_t aesiv[16];
MD5_CTX md5;

if (isSalted(data, datalen))
{
const uint8_t *salt = data + 8;
const size_t saltlen = 8;
data += 16;
datalen -= 16;

assert(aesNr == 10); // AES-256 needs more rounds.
assert(aesNk == 4); // hashing size is hardcoded later.
uint8_t hashing[32];

MD5_init(&md5);
MD5_append(&md5, key, keylen);
MD5_append(&md5, salt, saltlen);
MD5_finish(&md5, hashing);

MD5_init(&md5);
MD5_append(&md5, hashing, 16);
MD5_append(&md5, key, keylen);
MD5_append(&md5, salt, saltlen);
MD5_finish(&md5, &hashing[16]);

memcpy(aeskey, hashing, 4 * aesNk);
memcpy(aesiv, &hashing[4 * aesNk], 16);
} // if
else
{
MD5_init(&md5);
MD5_append(&md5, key, keylen);
MD5_finish(&md5, aeskey);
memset(aesiv, '\0', sizeof (aesiv));
} // else

if (decryptUsingKeyAndIvec(data, &datalen, aeskey, aesiv))
retvalStringBytes(L, data, datalen);
else
lua_pushnil(L);

free(dataptr);
return 1;
} // decryptBase64UsingKey


static void registerLuaLibs(lua_State *L)
{
// We always need the string and base libraries (although base has a
// few we could trim). The rest you can compile in if you want/need them.
int i;
static const luaL_Reg lualibs[] = {
{"_G", luaopen_base},
{LUA_STRLIBNAME, luaopen_string},
{LUA_TABLIBNAME, luaopen_table},
{LUA_LOADLIBNAME, luaopen_package},
{LUA_IOLIBNAME, luaopen_io},
{LUA_OSLIBNAME, luaopen_os},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_DBLIBNAME, luaopen_debug},
{LUA_BITLIBNAME, luaopen_bit32},
{LUA_COLIBNAME, luaopen_coroutine},
};

for (i = 0; i < STATICARRAYLEN(lualibs); i++)
{
luaL_requiref(L, lualibs[i].name, lualibs[i].func, 1);
lua_pop(L, 1); // remove lib
} // for
} // registerLuaLibs


static void *luaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
if (nsize == 0)
{
free(ptr);
return NULL;
} // if
return realloc(ptr, nsize);
} // luaAlloc


static inline void luaSetCFunc(lua_State *L, lua_CFunction f, const char *sym)
{
lua_pushcfunction(L, f);
lua_setglobal(luaState, sym);
} // luaSetCFunc


static int luaFatal(lua_State *L)
{
const char *errstr = lua_tostring(L, -1);
fprintf(stderr, "Lua panic: %s\n", errstr ? errstr : "(?)");
fflush(stderr);
exit(1);
} // luaFatal


static int initLua(void)
{
assert(luaState == NULL);
luaState = lua_newstate(luaAlloc, NULL);

lua_atpanic(luaState, luaFatal);
assert(lua_checkstack(luaState, 20)); // Just in case.
registerLuaLibs(luaState);

// Set up initial C functions, etc we want to expose to Lua code...
luaSetCFunc(luaState, decryptUsingPBKDF2, "decryptUsingPBKDF2");
luaSetCFunc(luaState, decryptBase64UsingKey, "decryptBase64UsingKey");

// Transfer control to Lua to setup some APIs and state...
if (luaL_dofile(luaState, "1pass.lua") != 0)
{
const char *msg = lua_tostring(luaState, -1);
fprintf(stderr, "1pass.lua didn't run: %s\n", msg);
lua_pop(luaState, 1);
return 0;
} // if

return 1;
} // initLua


void deinitLua(void)
{
if (luaState != NULL)
{
lua_close(luaState);
luaState = NULL;
} // if
} // deinitLua



static char *loadKey(const char *baseDir, const char *level, const char *password)
{
char *retval = NULL;
lua_getglobal(luaState, "loadKey");
lua_pushstring(luaState, baseDir);
lua_pushstring(luaState, level);
lua_pushstring(luaState, password);
lua_call(luaState, 3, 1);
const char *str = lua_tostring(luaState, -1);
if (str)
retval = strdup(str);
lua_pop(luaState, 1);
return retval;
} // luafunc_loadKey


static char *sl5 = NULL;

int main(int argc, char **argv)
{
const char *basedir = "1Password/1Password.agilekeychain/data/default"; // !!! FIXME

char *password = NULL;
size_t pwlen = 0;
printf("password: "); fflush(stdout);
const ssize_t rc = getline(&password, &pwlen, stdin);
if (rc == -1)
return 1;
else if (password[rc-1] == '\n')
password[rc-1] = 0;

if (!initLua())
{
fprintf(stderr, "uhoh\n");
return 1;
} // if

sl5 = loadKey(basedir, "SL5", password);
if (!sl5)
{
fprintf(stderr, "wrong password?\n");
return 1;
} // if

free(sl5);
return 0;
} // main

// end of 1pass.c ...

0 comments on commit 93a2c66

Please sign in to comment.