1pass.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 17 Dec 2013 22:16:27 -0500
changeset 0 d7ee4e2ed49d
child 1 0919d17b13f9
permissions -rw-r--r--
Initial work.

Goes far enough to decrypt and correctly verify the master key.
icculus@0
     1
#include <stdio.h>
icculus@0
     2
#include <stdlib.h>
icculus@0
     3
#include <string.h>
icculus@0
     4
#include <assert.h>
icculus@0
     5
#include "lua.h"
icculus@0
     6
#include "lauxlib.h"
icculus@0
     7
#include "lualib.h"
icculus@0
     8
#include "pkcs5_pbkdf2.h"
icculus@0
     9
#include "aes.h"
icculus@0
    10
#include "base64.h"
icculus@0
    11
#include "md5.h"
icculus@0
    12
icculus@0
    13
#define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) )
icculus@0
    14
icculus@0
    15
static lua_State *luaState = NULL;
icculus@0
    16
static const uint8_t zero16[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
icculus@0
    17
static const char saltprefix[] = { 'S', 'a', 'l', 't', 'e', 'd', '_', '_' };
icculus@0
    18
icculus@0
    19
icculus@0
    20
static inline int retvalString(lua_State *L, const char *str)
icculus@0
    21
{
icculus@0
    22
    if (str != NULL)
icculus@0
    23
        lua_pushstring(L, str);
icculus@0
    24
    else
icculus@0
    25
        lua_pushnil(L);
icculus@0
    26
    return 1;
icculus@0
    27
} // retvalString
icculus@0
    28
icculus@0
    29
icculus@0
    30
static inline int retvalStringBytes(lua_State *L, const uint8_t *str, size_t len)
icculus@0
    31
{
icculus@0
    32
//size_t i; printf("{\n"); for (i = 0; i < len; i++) { printf(" 0x%X\n", (unsigned int) str[i]); } printf(" }\n\n");
icculus@0
    33
icculus@0
    34
    if (str != NULL)
icculus@0
    35
        lua_pushlstring(L, (const char *) str, len);
icculus@0
    36
    else
icculus@0
    37
        lua_pushnil(L);
icculus@0
    38
    return 1;
icculus@0
    39
} // retvalStringBytes
icculus@0
    40
icculus@0
    41
static inline void xorBlock(uint8_t *dst, const uint8_t *src)
icculus@0
    42
{
icculus@0
    43
    int i;
icculus@0
    44
    for (i = 0; i < 16; i++, dst++, src++)
icculus@0
    45
        *dst ^= *src;
icculus@0
    46
} // xorBlock
icculus@0
    47
icculus@0
    48
static int decryptUsingKeyAndIvec(uint8_t *data, size_t *datalen, const uint8_t *key, const uint8_t *iv)
icculus@0
    49
{
icculus@0
    50
    const size_t blocks = *datalen / 16;
icculus@0
    51
    uint8_t *block = data + ((blocks-1) * 16);   // start at final block, work backwards
icculus@0
    52
    const uint8_t *padding = &block[15];
icculus@0
    53
    uint8_t expkey[aesExpandedKeySize];
icculus@0
    54
    size_t i;
icculus@0
    55
icculus@0
    56
    if (blocks == 0)
icculus@0
    57
        return 1;  // nothing to do.
icculus@0
    58
icculus@0
    59
	aesExpandKey(key, expkey);
icculus@0
    60
icculus@0
    61
    for (i = 0; i < blocks-1; i++)
icculus@0
    62
    {
icculus@0
    63
        aesDecrypt(block, expkey, block);   // decrypt in place.
icculus@0
    64
        xorBlock(block, block-16);
icculus@0
    65
        block -= 16;
icculus@0
    66
    }
icculus@0
    67
    aesDecrypt(block, expkey, block);   // decrypt in place.
icculus@0
    68
    xorBlock(block, iv);   // xor against initial vector for final block.
icculus@0
    69
icculus@0
    70
    if (*padding > 16)
icculus@0
    71
        return 0;  // bad data?
icculus@0
    72
icculus@0
    73
    *datalen -= *padding;
icculus@0
    74
icculus@0
    75
    return 1;
icculus@0
    76
} // decryptBinaryUsingKeyAndIvec
icculus@0
    77
icculus@0
    78
icculus@0
    79
static inline int isSalted(const uint8_t *data, const size_t datalen)
icculus@0
    80
{
icculus@0
    81
    return ( (datalen > sizeof (saltprefix)) &&
icculus@0
    82
             (memcmp(data, saltprefix, sizeof (saltprefix)) == 0) );
icculus@0
    83
} // isSalted
icculus@0
    84
icculus@0
    85
icculus@0
    86
static int decryptUsingPBKDF2(lua_State *L)
icculus@0
    87
{
icculus@0
    88
    const char *base64 = luaL_checkstring(L, 1);
icculus@0
    89
    const char *password = luaL_checkstring(L, 2);
icculus@0
    90
    const int iterations = luaL_checkinteger(L, 3);
icculus@0
    91
    size_t datalen = strlen(base64);
icculus@0
    92
    uint8_t *dataptr = (uint8_t *) malloc(datalen);
icculus@0
    93
    uint8_t *data = dataptr;
icculus@0
    94
    base64_decodestate base64state;
icculus@0
    95
icculus@0
    96
    base64_init_decodestate(&base64state);
icculus@0
    97
    datalen = base64_decode_block(base64, (int) datalen, data, &base64state);
icculus@0
    98
icculus@0
    99
    const uint8_t *salt = zero16;
icculus@0
   100
    int saltlen = sizeof (zero16);
icculus@0
   101
    if (isSalted(data, datalen))
icculus@0
   102
    {
icculus@0
   103
        salt = data + 8;
icculus@0
   104
        saltlen = 8;
icculus@0
   105
        data += 16;
icculus@0
   106
        datalen -= 16;
icculus@0
   107
    } // if
icculus@0
   108
icculus@0
   109
    uint8_t output[32];
icculus@0
   110
    pkcs5_pbkdf2(password, strlen(password), salt, saltlen, output, sizeof (output), (unsigned int) iterations);
icculus@0
   111
icculus@0
   112
    const uint8_t *aeskey = &output[0];
icculus@0
   113
    const uint8_t *aesiv = &output[16];
icculus@0
   114
	if (decryptUsingKeyAndIvec(data, &datalen, aeskey, aesiv))
icculus@0
   115
        retvalStringBytes(L, data, datalen);
icculus@0
   116
    else
icculus@0
   117
        lua_pushnil(L);
icculus@0
   118
icculus@0
   119
    free(dataptr);
icculus@0
   120
    return 1;
icculus@0
   121
} // decryptUsingPBKDF2
icculus@0
   122
icculus@0
   123
icculus@0
   124
static int decryptBase64UsingKey(lua_State *L)
icculus@0
   125
{
icculus@0
   126
    size_t keylen = 0;
icculus@0
   127
    const char *base64 = luaL_checkstring(L, 1);
icculus@0
   128
    const uint8_t *key = (const uint8_t *) luaL_checklstring(L, 2, &keylen);
icculus@0
   129
    size_t datalen = strlen(base64);
icculus@0
   130
    uint8_t *dataptr = (uint8_t *) malloc(datalen);
icculus@0
   131
    uint8_t *data = dataptr;
icculus@0
   132
    base64_decodestate base64state;
icculus@0
   133
icculus@0
   134
    base64_init_decodestate(&base64state);
icculus@0
   135
    datalen = base64_decode_block(base64, (int) datalen, data, &base64state);
icculus@0
   136
icculus@0
   137
    uint8_t aeskey[16];
icculus@0
   138
    uint8_t aesiv[16];
icculus@0
   139
    MD5_CTX md5;
icculus@0
   140
icculus@0
   141
    if (isSalted(data, datalen))
icculus@0
   142
    {
icculus@0
   143
        const uint8_t *salt = data + 8;
icculus@0
   144
        const size_t saltlen = 8;
icculus@0
   145
        data += 16;
icculus@0
   146
        datalen -= 16;
icculus@0
   147
icculus@0
   148
        assert(aesNr == 10);  // AES-256 needs more rounds.
icculus@0
   149
        assert(aesNk == 4);   // hashing size is hardcoded later.
icculus@0
   150
        uint8_t hashing[32];
icculus@0
   151
icculus@0
   152
        MD5_init(&md5);
icculus@0
   153
        MD5_append(&md5, key, keylen);
icculus@0
   154
        MD5_append(&md5, salt, saltlen);
icculus@0
   155
        MD5_finish(&md5, hashing);
icculus@0
   156
icculus@0
   157
        MD5_init(&md5);
icculus@0
   158
        MD5_append(&md5, hashing, 16);
icculus@0
   159
        MD5_append(&md5, key, keylen);
icculus@0
   160
        MD5_append(&md5, salt, saltlen);
icculus@0
   161
        MD5_finish(&md5, &hashing[16]);
icculus@0
   162
icculus@0
   163
        memcpy(aeskey, hashing, 4 * aesNk);
icculus@0
   164
        memcpy(aesiv, &hashing[4 * aesNk], 16);
icculus@0
   165
    } // if
icculus@0
   166
    else
icculus@0
   167
    {
icculus@0
   168
        MD5_init(&md5);
icculus@0
   169
        MD5_append(&md5, key, keylen);
icculus@0
   170
        MD5_finish(&md5, aeskey);
icculus@0
   171
        memset(aesiv, '\0', sizeof (aesiv));
icculus@0
   172
    } // else
icculus@0
   173
icculus@0
   174
	if (decryptUsingKeyAndIvec(data, &datalen, aeskey, aesiv))
icculus@0
   175
        retvalStringBytes(L, data, datalen);
icculus@0
   176
    else
icculus@0
   177
        lua_pushnil(L);
icculus@0
   178
icculus@0
   179
    free(dataptr);
icculus@0
   180
    return 1;
icculus@0
   181
} // decryptBase64UsingKey
icculus@0
   182
icculus@0
   183
icculus@0
   184
static void registerLuaLibs(lua_State *L)
icculus@0
   185
{
icculus@0
   186
    // We always need the string and base libraries (although base has a
icculus@0
   187
    //  few we could trim). The rest you can compile in if you want/need them.
icculus@0
   188
    int i;
icculus@0
   189
    static const luaL_Reg lualibs[] = {
icculus@0
   190
        {"_G", luaopen_base},
icculus@0
   191
        {LUA_STRLIBNAME, luaopen_string},
icculus@0
   192
        {LUA_TABLIBNAME, luaopen_table},
icculus@0
   193
        {LUA_LOADLIBNAME, luaopen_package},
icculus@0
   194
        {LUA_IOLIBNAME, luaopen_io},
icculus@0
   195
        {LUA_OSLIBNAME, luaopen_os},
icculus@0
   196
        {LUA_MATHLIBNAME, luaopen_math},
icculus@0
   197
        {LUA_DBLIBNAME, luaopen_debug},
icculus@0
   198
        {LUA_BITLIBNAME, luaopen_bit32},
icculus@0
   199
        {LUA_COLIBNAME, luaopen_coroutine},
icculus@0
   200
    };
icculus@0
   201
icculus@0
   202
    for (i = 0; i < STATICARRAYLEN(lualibs); i++)
icculus@0
   203
    {
icculus@0
   204
        luaL_requiref(L, lualibs[i].name, lualibs[i].func, 1);
icculus@0
   205
        lua_pop(L, 1);  // remove lib
icculus@0
   206
    } // for
icculus@0
   207
} // registerLuaLibs
icculus@0
   208
icculus@0
   209
icculus@0
   210
static void *luaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
icculus@0
   211
{
icculus@0
   212
    if (nsize == 0)
icculus@0
   213
    {
icculus@0
   214
        free(ptr);
icculus@0
   215
        return NULL;
icculus@0
   216
    } // if
icculus@0
   217
    return realloc(ptr, nsize);
icculus@0
   218
} // luaAlloc
icculus@0
   219
icculus@0
   220
icculus@0
   221
static inline void luaSetCFunc(lua_State *L, lua_CFunction f, const char *sym)
icculus@0
   222
{
icculus@0
   223
    lua_pushcfunction(L, f);
icculus@0
   224
    lua_setglobal(luaState, sym);
icculus@0
   225
} // luaSetCFunc
icculus@0
   226
icculus@0
   227
icculus@0
   228
static int luaFatal(lua_State *L)
icculus@0
   229
{
icculus@0
   230
    const char *errstr = lua_tostring(L, -1);
icculus@0
   231
    fprintf(stderr, "Lua panic: %s\n", errstr ? errstr : "(?)");
icculus@0
   232
    fflush(stderr);
icculus@0
   233
    exit(1);
icculus@0
   234
} // luaFatal
icculus@0
   235
icculus@0
   236
icculus@0
   237
static int initLua(void)
icculus@0
   238
{
icculus@0
   239
    assert(luaState == NULL);
icculus@0
   240
    luaState = lua_newstate(luaAlloc, NULL);
icculus@0
   241
icculus@0
   242
    lua_atpanic(luaState, luaFatal);
icculus@0
   243
    assert(lua_checkstack(luaState, 20));  // Just in case.
icculus@0
   244
    registerLuaLibs(luaState);
icculus@0
   245
icculus@0
   246
    // Set up initial C functions, etc we want to expose to Lua code...
icculus@0
   247
    luaSetCFunc(luaState, decryptUsingPBKDF2, "decryptUsingPBKDF2");
icculus@0
   248
    luaSetCFunc(luaState, decryptBase64UsingKey, "decryptBase64UsingKey");
icculus@0
   249
icculus@0
   250
    // Transfer control to Lua to setup some APIs and state...
icculus@0
   251
    if (luaL_dofile(luaState, "1pass.lua") != 0)
icculus@0
   252
    {
icculus@0
   253
        const char *msg = lua_tostring(luaState, -1);
icculus@0
   254
        fprintf(stderr, "1pass.lua didn't run: %s\n", msg);
icculus@0
   255
        lua_pop(luaState, 1);
icculus@0
   256
        return 0;
icculus@0
   257
    } // if
icculus@0
   258
icculus@0
   259
    return 1;
icculus@0
   260
} // initLua
icculus@0
   261
icculus@0
   262
icculus@0
   263
void deinitLua(void)
icculus@0
   264
{
icculus@0
   265
    if (luaState != NULL)
icculus@0
   266
    {
icculus@0
   267
        lua_close(luaState);
icculus@0
   268
        luaState = NULL;
icculus@0
   269
    } // if
icculus@0
   270
} // deinitLua
icculus@0
   271
icculus@0
   272
icculus@0
   273
icculus@0
   274
static char *loadKey(const char *baseDir, const char *level, const char *password)
icculus@0
   275
{
icculus@0
   276
    char *retval = NULL;
icculus@0
   277
    lua_getglobal(luaState, "loadKey");
icculus@0
   278
    lua_pushstring(luaState, baseDir);
icculus@0
   279
    lua_pushstring(luaState, level);
icculus@0
   280
    lua_pushstring(luaState, password);
icculus@0
   281
    lua_call(luaState, 3, 1);
icculus@0
   282
    const char *str = lua_tostring(luaState, -1);
icculus@0
   283
    if (str)
icculus@0
   284
        retval = strdup(str);
icculus@0
   285
    lua_pop(luaState, 1);
icculus@0
   286
    return retval;
icculus@0
   287
} // luafunc_loadKey
icculus@0
   288
icculus@0
   289
icculus@0
   290
static char *sl5 = NULL;
icculus@0
   291
icculus@0
   292
int main(int argc, char **argv)
icculus@0
   293
{
icculus@0
   294
    const char *basedir = "1Password/1Password.agilekeychain/data/default";  // !!! FIXME
icculus@0
   295
icculus@0
   296
    char *password = NULL;
icculus@0
   297
    size_t pwlen = 0;
icculus@0
   298
    printf("password: "); fflush(stdout);
icculus@0
   299
    const ssize_t rc = getline(&password, &pwlen, stdin);
icculus@0
   300
    if (rc == -1)
icculus@0
   301
        return 1;
icculus@0
   302
    else if (password[rc-1] == '\n')
icculus@0
   303
        password[rc-1] = 0;
icculus@0
   304
icculus@0
   305
    if (!initLua())
icculus@0
   306
    {
icculus@0
   307
        fprintf(stderr, "uhoh\n");
icculus@0
   308
        return 1;
icculus@0
   309
    } // if
icculus@0
   310
icculus@0
   311
    sl5 = loadKey(basedir, "SL5", password);
icculus@0
   312
    if (!sl5)
icculus@0
   313
    {
icculus@0
   314
        fprintf(stderr, "wrong password?\n");
icculus@0
   315
        return 1;
icculus@0
   316
    } // if
icculus@0
   317
icculus@0
   318
    free(sl5);
icculus@0
   319
    return 0;
icculus@0
   320
} // main
icculus@0
   321
icculus@0
   322
// end of 1pass.c ...
icculus@0
   323