opvault.c
author Ryan C. Gordon <icculus@icculus.org>
Fri, 23 Jun 2017 17:28:03 -0400
changeset 58 1390348facc7
permissions -rw-r--r--
Command line tool that decrypts an OPVault keychain and dumps it to stdout.

To compile: gcc -o opvault opvault.c cJSON.c -lcrypto

Usage: ./opvault </path/to/mykeychain.opvault> <password>

This is just a proof of concept; I'll be recycling this into proper OPVault
support in 1pass later and deleting this tool.

This uses OpenSSL's libcrypto for the math instead of all the homegrown
crypto this project is otherwise using. I'll probably migrate the rest in
this direction, too, since this wasn't as bad as I expected to use and
gets you all the package-manager mojo of automatic bug fixes and security
patches and shared code, etc.

cJSON parses JSON in C. That is from https://github.com/DaveGamble/cJSON

An example OPVault keychain from AgileBits is available here:

https://cache.agilebits.com/security-kb/
icculus@58
     1
// https://support.1password.com/opvault-design
icculus@58
     2
icculus@58
     3
#include <stdio.h>
icculus@58
     4
#include <stdlib.h>
icculus@58
     5
#include <string.h>
icculus@58
     6
#include <stdint.h>
icculus@58
     7
#include <unistd.h>
icculus@58
     8
#include <time.h>
icculus@58
     9
#include <errno.h>
icculus@58
    10
icculus@58
    11
#include <openssl/bio.h>
icculus@58
    12
#include <openssl/evp.h>
icculus@58
    13
#include <openssl/hmac.h>
icculus@58
    14
#include <openssl/err.h>
icculus@58
    15
#include <openssl/conf.h>
icculus@58
    16
icculus@58
    17
#include "cJSON.h"
icculus@58
    18
icculus@58
    19
#define JSONVAR(typ, var, jstyp, json, name) typ var; { \
icculus@58
    20
    cJSON *item = cJSON_GetObjectItem(json, name); \
icculus@58
    21
    if (!item) { \
icculus@58
    22
        fprintf(stderr, "No " name " field in profile.\n"); \
icculus@58
    23
        return 0; \
icculus@58
    24
    } \
icculus@58
    25
    var = (typ) item->value##jstyp; \
icculus@58
    26
}
icculus@58
    27
icculus@58
    28
static cJSON *load_json(const char *fname)
icculus@58
    29
{
icculus@58
    30
    cJSON *retval = NULL;
icculus@58
    31
    FILE *io = fopen(fname, "rb");
icculus@58
    32
icculus@58
    33
    if (io != NULL) {
icculus@58
    34
        if (fseek(io, 0, SEEK_END) == 0) {
icculus@58
    35
            long len = ftell(io);
icculus@58
    36
            if ((len != -1) && (fseek(io, 0, SEEK_SET) == 0)) {
icculus@58
    37
                char *buf = (char *) malloc(len + 1);
icculus@58
    38
                if ((buf != NULL) && (fread(buf, len, 1, io) == 1)) {
icculus@58
    39
                    char *json = buf;
icculus@58
    40
                    json[len] = '\0';
icculus@58
    41
icculus@58
    42
                    // !!! FIXME: hack.
icculus@58
    43
                    if (strncmp(json, "ld(", 3) == 0) {
icculus@58
    44
                        json[len-2] = '\0';  // chop off ");" from end.
icculus@58
    45
                        json += 3;  // skip past "ld(".
icculus@58
    46
                        len -= 5;
icculus@58
    47
                    } else if (strncmp(json, "loadFolders(", 12) == 0) {
icculus@58
    48
                        json[len-2] = '\0';  // chop off ");" from end.
icculus@58
    49
                        json += 12;  // skip past "loadFolders(".
icculus@58
    50
                        len -= 14;
icculus@58
    51
                    } else if (strncmp(json, "var profile=", 12) == 0) {
icculus@58
    52
                        json[len-1] = '\0';  // chop off ";" from end.
icculus@58
    53
                        json += 12;  // skip past "var profile=".
icculus@58
    54
                        len -= 13;
icculus@58
    55
                    }
icculus@58
    56
icculus@58
    57
                    retval = cJSON_Parse(json);
icculus@58
    58
                }
icculus@58
    59
                free(buf);
icculus@58
    60
            }
icculus@58
    61
        }
icculus@58
    62
        fclose(io);
icculus@58
    63
    }
icculus@58
    64
icculus@58
    65
    return retval;
icculus@58
    66
}
icculus@58
    67
icculus@58
    68
static void dump_json_internal(const cJSON *json, const int indent)
icculus@58
    69
{
icculus@58
    70
    const cJSON *i;
icculus@58
    71
    int j;
icculus@58
    72
icculus@58
    73
    if (!json) return;
icculus@58
    74
icculus@58
    75
    for (j = 0; j < (indent*2); j++) {
icculus@58
    76
        printf(" ");
icculus@58
    77
    }
icculus@58
    78
icculus@58
    79
    if (json->string != NULL) {
icculus@58
    80
        printf("%s : ", json->string);
icculus@58
    81
    }
icculus@58
    82
icculus@58
    83
    switch (json->type) {
icculus@58
    84
        default: printf("[!unknown type!]"); break;
icculus@58
    85
        case cJSON_Invalid: printf("[!invalid!]"); break;
icculus@58
    86
        case cJSON_False: printf("false"); break;
icculus@58
    87
        case cJSON_True: printf("true"); break;
icculus@58
    88
        case cJSON_NULL: printf("null"); break;
icculus@58
    89
        case cJSON_Number: printf("%f", json->valuedouble); break;
icculus@58
    90
        case cJSON_Raw: printf("!CDATA[\"%s\"]", json->valuestring); break;
icculus@58
    91
        case cJSON_String: printf("\"%s\"", json->valuestring); break;
icculus@58
    92
icculus@58
    93
        case cJSON_Array:
icculus@58
    94
            printf("[\n");
icculus@58
    95
            for (i = json->child; i != NULL; i = i->next) {
icculus@58
    96
                dump_json_internal(i, indent + 1);
icculus@58
    97
                if (i->next != NULL) {
icculus@58
    98
                    printf(", ");
icculus@58
    99
                }
icculus@58
   100
                printf("\n");
icculus@58
   101
            }
icculus@58
   102
            for (j = 0; j < (indent*2); j++) {
icculus@58
   103
                printf(" ");
icculus@58
   104
            }
icculus@58
   105
            printf("]");
icculus@58
   106
            break;
icculus@58
   107
icculus@58
   108
        case cJSON_Object:
icculus@58
   109
            printf("{\n");
icculus@58
   110
            for (i = json->child; i != NULL; i = i->next) {
icculus@58
   111
                dump_json_internal(i, indent + 1);
icculus@58
   112
                if (i->next != NULL) {
icculus@58
   113
                    printf(", ");
icculus@58
   114
                }
icculus@58
   115
                printf("\n");
icculus@58
   116
            }
icculus@58
   117
            for (j = 0; j < (indent*2); j++) {
icculus@58
   118
                printf(" ");
icculus@58
   119
            }
icculus@58
   120
            printf("}");
icculus@58
   121
            break;
icculus@58
   122
    }
icculus@58
   123
}
icculus@58
   124
icculus@58
   125
static void dump_json(const cJSON *json)
icculus@58
   126
{
icculus@58
   127
    dump_json_internal(json, 0);
icculus@58
   128
    printf("\n");
icculus@58
   129
}
icculus@58
   130
icculus@58
   131
icculus@58
   132
static int base64_decode(const char *in, const int inlen, uint8_t **out)
icculus@58
   133
{
icculus@58
   134
    const int len = (inlen == -1) ? (int) strlen(in) : inlen;
icculus@58
   135
icculus@58
   136
    *out = NULL;
icculus@58
   137
icculus@58
   138
    BIO *b64f = BIO_new(BIO_f_base64());
icculus@58
   139
    BIO *buff = buff = BIO_push(b64f, BIO_new_mem_buf(in, len));
icculus@58
   140
icculus@58
   141
    uint8_t *decoded = (uint8_t *) malloc(len);
icculus@58
   142
    if (!decoded) {
icculus@58
   143
        fprintf(stderr, "Out of memory!\n");
icculus@58
   144
        return -1;
icculus@58
   145
    }
icculus@58
   146
icculus@58
   147
    BIO_set_flags(buff, BIO_FLAGS_BASE64_NO_NL);
icculus@58
   148
    BIO_set_close(buff, BIO_CLOSE);
icculus@58
   149
    const int retval = BIO_read(buff, decoded, len);
icculus@58
   150
    if (retval < 0) {
icculus@58
   151
        free(decoded);
icculus@58
   152
        return -1;
icculus@58
   153
    }
icculus@58
   154
    decoded[retval] = '\0';
icculus@58
   155
icculus@58
   156
    BIO_free_all(buff);
icculus@58
   157
icculus@58
   158
    *out = decoded;
icculus@58
   159
    return retval;
icculus@58
   160
}
icculus@58
   161
icculus@58
   162
static int decrypt_opdata(const char *name, const uint8_t *opdata, const int opdatalen, const uint8_t *encryptionkey, const uint8_t *mackey, uint8_t **out, int *outlen)
icculus@58
   163
{
icculus@58
   164
    *out = NULL;
icculus@58
   165
    if (outlen) {
icculus@58
   166
        *outlen = 0;
icculus@58
   167
    }
icculus@58
   168
icculus@58
   169
    if ((opdatalen < 64) || (memcmp(opdata, "opdata01", 8) != 0)) {
icculus@58
   170
        fprintf(stderr, "opdata(%s) isn't actually in opdata01 format.\n", name);
icculus@58
   171
        return 0;
icculus@58
   172
    }
icculus@58
   173
icculus@58
   174
    // !!! FIXME: byteswap
icculus@58
   175
    const int plaintextlen = (int) (*((uint64_t *) (opdata + 8)));
icculus@58
   176
    const int paddedplaintextlen = plaintextlen + (16 - (plaintextlen % 16));
icculus@58
   177
    if (paddedplaintextlen > (opdatalen - (8 + 8 + 16 + 32))) {  // minus magic, len, iv, hmac
icculus@58
   178
        fprintf(stderr, "opdata(%s) plaintext length is bogus.\n", name);
icculus@58
   179
        return 0;
icculus@58
   180
    }
icculus@58
   181
icculus@58
   182
    uint8_t digest[32];
icculus@58
   183
    unsigned int digestlen = sizeof (digest);
icculus@58
   184
    if (!HMAC(EVP_sha256(), mackey, 32, opdata, opdatalen-32, (unsigned char *) digest, &digestlen)) {
icculus@58
   185
        fprintf(stderr, "opdata(%s) HMAC failed.\n", name);
icculus@58
   186
        return 0;
icculus@58
   187
    } else if (digestlen != sizeof (digest)) {
icculus@58
   188
        fprintf(stderr, "opdata(%s) HMAC is wrong size (got=%u expected=%u).\n", name, digestlen, (unsigned int) sizeof (digest));
icculus@58
   189
        return 0;
icculus@58
   190
    } else if (memcmp(digest, opdata + (opdatalen-sizeof (digest)), sizeof (digest)) != 0) {
icculus@58
   191
        fprintf(stderr, "opdata(%s) HMAC verification failed.\n", name);
icculus@58
   192
        return 0;
icculus@58
   193
    }
icculus@58
   194
icculus@58
   195
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
icculus@58
   196
    if (!ctx) {
icculus@58
   197
        fprintf(stderr, "opdata(%s) EVP_CIPHER_CTX_new() failed\n", name);
icculus@58
   198
        return 0;
icculus@58
   199
    }
icculus@58
   200
icculus@58
   201
    const unsigned char *iv = (unsigned char *) (opdata + 16);
icculus@58
   202
    if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char *) encryptionkey, iv)) {
icculus@58
   203
        fprintf(stderr, "opdata(%s) EVP_DecryptInit_ex() failed\n", name);
icculus@58
   204
        EVP_CIPHER_CTX_cleanup(ctx);
icculus@58
   205
        return 0;
icculus@58
   206
    }
icculus@58
   207
icculus@58
   208
    EVP_CIPHER_CTX_set_padding(ctx, 0);
icculus@58
   209
icculus@58
   210
    uint8_t *plaintext = (uint8_t *) malloc(paddedplaintextlen);
icculus@58
   211
    if (!plaintext) {
icculus@58
   212
        fprintf(stderr, "opdata(%s) Out of memory.\n", name);
icculus@58
   213
        EVP_CIPHER_CTX_cleanup(ctx);
icculus@58
   214
        return 0;
icculus@58
   215
    }
icculus@58
   216
icculus@58
   217
    // opdata+32 == first byte past magic, len, and iv.
icculus@58
   218
    int decryptedlen = 0;
icculus@58
   219
    if (!EVP_DecryptUpdate(ctx, plaintext, &decryptedlen, opdata + 32, paddedplaintextlen)) {
icculus@58
   220
        fprintf(stderr, "opdata(%s) EVP_DecryptUpdate() failed\n", name);
icculus@58
   221
        free(plaintext);
icculus@58
   222
        EVP_CIPHER_CTX_cleanup(ctx);
icculus@58
   223
        return 0;
icculus@58
   224
    }
icculus@58
   225
icculus@58
   226
    int totaldecryptedlen = decryptedlen;
icculus@58
   227
    if (!EVP_DecryptFinal_ex(ctx, plaintext + decryptedlen, &decryptedlen)) {
icculus@58
   228
        fprintf(stderr, "opdata(%s) EVP_DecryptFinal_ex() failed\n", name);
icculus@58
   229
        free(plaintext);
icculus@58
   230
        EVP_CIPHER_CTX_cleanup(ctx);
icculus@58
   231
        return 0;
icculus@58
   232
    }
icculus@58
   233
    totaldecryptedlen += decryptedlen;
icculus@58
   234
icculus@58
   235
    EVP_CIPHER_CTX_cleanup(ctx);
icculus@58
   236
icculus@58
   237
    if (totaldecryptedlen != paddedplaintextlen) {
icculus@58
   238
        fprintf(stderr, "opdata(%s) decrypted to wrong size (got=%d expected=%u).\n", name, totaldecryptedlen, (unsigned int) paddedplaintextlen);
icculus@58
   239
        free(plaintext);
icculus@58
   240
        return 0;
icculus@58
   241
    }
icculus@58
   242
icculus@58
   243
    // random padding bytes are prepended. Drop them.
icculus@58
   244
    const int paddinglen = paddedplaintextlen - plaintextlen;
icculus@58
   245
    memmove(plaintext, plaintext + paddinglen, plaintextlen);
icculus@58
   246
icculus@58
   247
    *out = plaintext;
icculus@58
   248
    if (outlen) {
icculus@58
   249
        *outlen = plaintextlen;
icculus@58
   250
    }
icculus@58
   251
icculus@58
   252
    return 1;
icculus@58
   253
}
icculus@58
   254
icculus@58
   255
static int decrypt_opdata_base64(const char *name, const char *base64data, const uint8_t *encryptionkey, const uint8_t *mackey, uint8_t **out, int *outlen)
icculus@58
   256
{
icculus@58
   257
    uint8_t *opdata = NULL;
icculus@58
   258
    const int opdatalen = base64_decode(base64data, -1, &opdata);
icculus@58
   259
    if (opdatalen == -1) {
icculus@58
   260
        fprintf(stderr, "opdata(%s) wasn't a valid base64 string\n", name);
icculus@58
   261
        return 0;
icculus@58
   262
    }
icculus@58
   263
icculus@58
   264
    const int retval = decrypt_opdata(name, opdata, opdatalen, encryptionkey, mackey, out, outlen);
icculus@58
   265
    free(opdata);
icculus@58
   266
    return retval;
icculus@58
   267
}
icculus@58
   268
icculus@58
   269
static int decrypt_key(const char *name, const char *base64data, const uint8_t *encryptionkey, const uint8_t *mackey, uint8_t *finalkey, uint8_t *finalhmac)
icculus@58
   270
{
icculus@58
   271
    uint8_t *decryptedkey = NULL;
icculus@58
   272
    int decryptedkeylen = 0;
icculus@58
   273
    if (!decrypt_opdata_base64(name, base64data, encryptionkey, mackey, &decryptedkey, &decryptedkeylen)) {
icculus@58
   274
        return 0;
icculus@58
   275
    }
icculus@58
   276
icculus@58
   277
    uint8_t digest[64];
icculus@58
   278
    unsigned int digestlen = sizeof (digest);
icculus@58
   279
    const int rc = EVP_Digest(decryptedkey, decryptedkeylen, (unsigned char *) digest, &digestlen, EVP_sha512(), NULL);
icculus@58
   280
    free(decryptedkey);
icculus@58
   281
    if (!rc) {
icculus@58
   282
        fprintf(stderr, "Hashing %s failed.\n", name);
icculus@58
   283
        return 0;
icculus@58
   284
    } else if (digestlen != sizeof (digest)) {
icculus@58
   285
        fprintf(stderr, "Hash for %s is wrong size (got=%u expected=%u).\n", name, digestlen, (unsigned int) sizeof (digest));
icculus@58
   286
        return 0;
icculus@58
   287
    }
icculus@58
   288
icculus@58
   289
    memcpy(finalkey, digest, 32);
icculus@58
   290
    memcpy(finalhmac, digest + 32, 32);
icculus@58
   291
    return 1;
icculus@58
   292
}
icculus@58
   293
icculus@58
   294
static int derive_keys_from_password(const char *password, const char *base64salt, const int iterations, uint8_t *encryptionkey, uint8_t *mackey)
icculus@58
   295
{
icculus@58
   296
    uint8_t salt[16];
icculus@58
   297
    uint8_t *buf = NULL;
icculus@58
   298
    int saltlen = base64_decode(base64salt, -1, &buf);
icculus@58
   299
    if (saltlen == -1) {
icculus@58
   300
        fprintf(stderr, "Salt wasn't a valid base64 string.\n");
icculus@58
   301
        return 0;
icculus@58
   302
    } else if (saltlen != 16) {
icculus@58
   303
        fprintf(stderr, "Expected salt to base64-decode to 16 bytes (it was %lu).\n", (unsigned long) saltlen);
icculus@58
   304
        free(buf);
icculus@58
   305
        return 0;
icculus@58
   306
    }
icculus@58
   307
    memcpy(salt, buf, saltlen);
icculus@58
   308
    free(buf);
icculus@58
   309
icculus@58
   310
    uint8_t derived[64];
icculus@58
   311
    if (!PKCS5_PBKDF2_HMAC(password, -1,
icculus@58
   312
            (const unsigned char *) salt, saltlen, iterations,
icculus@58
   313
            EVP_sha512(), sizeof (derived), (unsigned char *) derived)) {
icculus@58
   314
        fprintf(stderr, "Key derivation failed.\n");
icculus@58
   315
        return 0;
icculus@58
   316
    }
icculus@58
   317
icculus@58
   318
    // first half of the derived key is the encryption key, second half is MAC key.
icculus@58
   319
    memcpy(encryptionkey, derived, 32);
icculus@58
   320
    memcpy(mackey, derived + 32, 32);
icculus@58
   321
    return 1;
icculus@58
   322
}
icculus@58
   323
icculus@58
   324
static int prepare_keys(cJSON *profile, const char *password,
icculus@58
   325
                        uint8_t *masterkey, uint8_t *masterhmac,
icculus@58
   326
                        uint8_t *overviewkey, uint8_t *overviewhmac)
icculus@58
   327
{
icculus@58
   328
    JSONVAR(const char *, base64salt, string, profile, "salt");
icculus@58
   329
    JSONVAR(const char *, base64masterkey, string, profile, "masterKey");
icculus@58
   330
    JSONVAR(const char *, base64overviewkey, string, profile, "overviewKey");
icculus@58
   331
    JSONVAR(int, iterations, double, profile, "iterations");
icculus@58
   332
icculus@58
   333
    uint8_t encryptionkey[32];
icculus@58
   334
    uint8_t mackey[32];
icculus@58
   335
    if (!derive_keys_from_password(password, base64salt, iterations, encryptionkey, mackey)) {
icculus@58
   336
        return 0;
icculus@58
   337
    } else if (!decrypt_key("master key", base64masterkey, encryptionkey, mackey, masterkey, masterhmac)) {
icculus@58
   338
        return 0;
icculus@58
   339
    } else if (!decrypt_key("overview key", base64overviewkey, encryptionkey, mackey, overviewkey, overviewhmac)) {
icculus@58
   340
        return 0;
icculus@58
   341
    }
icculus@58
   342
icculus@58
   343
    return 1;
icculus@58
   344
}
icculus@58
   345
icculus@58
   346
static void dump_folders(const uint8_t *overviewkey, const uint8_t *overviewhmac)
icculus@58
   347
{
icculus@58
   348
    cJSON *folders = load_json("folders.js");
icculus@58
   349
    cJSON *i;
icculus@58
   350
icculus@58
   351
    if (!folders || !folders->child) {
icculus@58
   352
        printf("(no folders.)\n");
icculus@58
   353
        return;
icculus@58
   354
    }
icculus@58
   355
icculus@58
   356
    printf("\nFolders...\n");
icculus@58
   357
    for (i = folders->child; i != NULL; i = i->next) {
icculus@58
   358
        char *encrypted = NULL;
icculus@58
   359
        uint8_t *decrypted = NULL;
icculus@58
   360
        cJSON *overview = cJSON_GetObjectItem(i, "overview");
icculus@58
   361
        if (overview) {
icculus@58
   362
            encrypted = overview->valuestring;
icculus@58
   363
            int decryptedlen = 0;
icculus@58
   364
            if (decrypt_opdata_base64("overview", encrypted, overviewkey, overviewhmac, &decrypted, &decryptedlen)) {
icculus@58
   365
                decrypted[decryptedlen] = 0;
icculus@58
   366
                overview->valuestring = (char *) decrypted;
icculus@58
   367
            }
icculus@58
   368
        }
icculus@58
   369
icculus@58
   370
        dump_json(i);
icculus@58
   371
        printf("\n");
icculus@58
   372
icculus@58
   373
        if (overview) {
icculus@58
   374
            overview->valuestring = encrypted; // put this back for cleanup.
icculus@58
   375
        }
icculus@58
   376
        free(decrypted);
icculus@58
   377
    }
icculus@58
   378
icculus@58
   379
    printf("\n");
icculus@58
   380
icculus@58
   381
    cJSON_Delete(folders);
icculus@58
   382
}
icculus@58
   383
icculus@58
   384
typedef struct CategoryMap
icculus@58
   385
{
icculus@58
   386
    const char *name;
icculus@58
   387
    const char *idstr;
icculus@58
   388
} CategoryMap;
icculus@58
   389
icculus@58
   390
static const CategoryMap category_map[] = {
icculus@58
   391
    { "Login", "001" },
icculus@58
   392
    { "Credit Card", "002" },
icculus@58
   393
    { "Secure Note", "003" },
icculus@58
   394
    { "Identity", "004" },
icculus@58
   395
    { "Password", "005" },
icculus@58
   396
    { "Tombstone", "099" },
icculus@58
   397
    { "Software License", "100" },
icculus@58
   398
    { "Bank Account", "101" },
icculus@58
   399
    { "Database", "102" },
icculus@58
   400
    { "Driver License", "103" },
icculus@58
   401
    { "Outdoor License", "104" },
icculus@58
   402
    { "Membership", "105" },
icculus@58
   403
    { "Passport", "106" },
icculus@58
   404
    { "Rewards", "107" },
icculus@58
   405
    { "SSN", "108" },
icculus@58
   406
    { "Router", "109" },
icculus@58
   407
    { "Server", "110" },
icculus@58
   408
    { "Email", "111" }
icculus@58
   409
};
icculus@58
   410
icculus@58
   411
static int compare_cjson_by_fieldname(const void *a, const void *b)
icculus@58
   412
{
icculus@58
   413
    return strcmp( (*(const cJSON **) a)->string, (*(const cJSON **) b)->string );
icculus@58
   414
}
icculus@58
   415
icculus@58
   416
static int test_item_hmac(cJSON *item, const char *base64hmac, const uint8_t *overviewhmac)
icculus@58
   417
{
icculus@58
   418
    // sort all the fields into alphabetic order.
icculus@58
   419
    int total = 0;
icculus@58
   420
    for (cJSON *i = item->child; i != NULL; i = i->next) {
icculus@58
   421
        total++;
icculus@58
   422
    }
icculus@58
   423
icculus@58
   424
    total--;  // don't include "hmac"
icculus@58
   425
icculus@58
   426
    cJSON **items = (cJSON **) calloc(total, sizeof (cJSON *));
icculus@58
   427
    if (!items) {
icculus@58
   428
        return 0;  // oh well.
icculus@58
   429
    }
icculus@58
   430
icculus@58
   431
    total = 0;
icculus@58
   432
    for (cJSON *i = item->child; i != NULL; i = i->next) {
icculus@58
   433
        if (strcmp(i->string, "hmac") == 0) {
icculus@58
   434
            continue;
icculus@58
   435
        }
icculus@58
   436
        items[total++] = i;
icculus@58
   437
    }
icculus@58
   438
icculus@58
   439
    qsort(items, total, sizeof (cJSON *), compare_cjson_by_fieldname);
icculus@58
   440
icculus@58
   441
    int retval = 0;
icculus@58
   442
    uint8_t digest[32];
icculus@58
   443
    HMAC_CTX ctx;
icculus@58
   444
    HMAC_CTX_init(&ctx);
icculus@58
   445
    if (HMAC_Init(&ctx, overviewhmac, 32, EVP_sha256())) {
icculus@58
   446
        int i;
icculus@58
   447
        for (i = 0; i < total; i++) {
icculus@58
   448
            cJSON *it = items[i];
icculus@58
   449
            if (!HMAC_Update(&ctx, (const unsigned char *) it->string, strlen(it->string))) {
icculus@58
   450
                break;
icculus@58
   451
            }
icculus@58
   452
icculus@58
   453
            char strbuf[64];
icculus@58
   454
            const char *str = NULL;
icculus@58
   455
            switch (it->type) {
icculus@58
   456
                case cJSON_False: str = "0"; break;
icculus@58
   457
                case cJSON_True: str = "1"; break;
icculus@58
   458
                case cJSON_Number: str = strbuf; snprintf(strbuf, sizeof (strbuf), "%d", (int) it->valuedouble); break;  // !!! FIXME: might be wrong.
icculus@58
   459
                case cJSON_String: str = it->valuestring; break;
icculus@58
   460
                default: fprintf(stderr, "uhoh, can't HMAC this field ('%s')!\n", it->string); break;
icculus@58
   461
            }
icculus@58
   462
icculus@58
   463
            if (!HMAC_Update(&ctx, (const unsigned char *) str, strlen(str))) {
icculus@58
   464
                break;
icculus@58
   465
            }
icculus@58
   466
        }
icculus@58
   467
icculus@58
   468
        unsigned int digestlen = sizeof (digest);
icculus@58
   469
        if ((i == total) && (HMAC_Final(&ctx, digest, &digestlen)) && (digestlen == sizeof (digest))) {
icculus@58
   470
            uint8_t *expected = NULL;
icculus@58
   471
            if (base64_decode(base64hmac, -1, &expected) == sizeof (digest)) {
icculus@58
   472
                retval = (memcmp(digest, expected, sizeof (digest)) == 0) ? 1 : 0;
icculus@58
   473
            }
icculus@58
   474
            free(expected);
icculus@58
   475
        }
icculus@58
   476
    }
icculus@58
   477
icculus@58
   478
    HMAC_CTX_cleanup(&ctx);
icculus@58
   479
    free(items);
icculus@58
   480
icculus@58
   481
    return retval;
icculus@58
   482
}
icculus@58
   483
icculus@58
   484
static void dump_band(cJSON *band, const uint8_t *masterkey, const uint8_t *masterhmac, const uint8_t *overviewkey, const uint8_t *overviewhmac)
icculus@58
   485
{
icculus@58
   486
    for (cJSON *i = band->child; i != NULL; i = i->next) {
icculus@58
   487
        //dump_json(i);
icculus@58
   488
        cJSON *json;
icculus@58
   489
        uint8_t itemkey[32];
icculus@58
   490
        uint8_t itemhmac[32];
icculus@58
   491
        int itemkeysokay = 0;
icculus@58
   492
icculus@58
   493
        printf("uuid %s:\n", i->string);
icculus@58
   494
icculus@58
   495
        if ((json = cJSON_GetObjectItem(i, "category")) != NULL) {
icculus@58
   496
            const char *category = json->valuestring;
icculus@58
   497
            for (int i = 0; i < sizeof (category_map) / sizeof (category_map[0]); i++) {
icculus@58
   498
                if (strcmp(category_map[i].idstr, category) == 0) {
icculus@58
   499
                    category = category_map[i].name;
icculus@58
   500
                    break;
icculus@58
   501
                }
icculus@58
   502
            }
icculus@58
   503
            printf(" category: %s\n", category);
icculus@58
   504
        }
icculus@58
   505
icculus@58
   506
        if ((json = cJSON_GetObjectItem(i, "created")) != NULL) {
icculus@58
   507
            time_t t = (time_t) json->valuedouble;
icculus@58
   508
            printf(" created: %s", ctime(&t));
icculus@58
   509
        }
icculus@58
   510
icculus@58
   511
        if ((json = cJSON_GetObjectItem(i, "updated")) != NULL) {
icculus@58
   512
            time_t t = (time_t) json->valuedouble;
icculus@58
   513
            printf(" updated: %s", ctime(&t));
icculus@58
   514
        }
icculus@58
   515
icculus@58
   516
        if ((json = cJSON_GetObjectItem(i, "tx")) != NULL) {
icculus@58
   517
            time_t t = (time_t) json->valuedouble;
icculus@58
   518
            printf(" last tx: %s", ctime(&t));
icculus@58
   519
        }
icculus@58
   520
icculus@58
   521
        printf(" trashed: %s\n", cJSON_IsTrue(cJSON_GetObjectItem(i, "trashed")) ? "true" : "false");
icculus@58
   522
icculus@58
   523
        json = cJSON_GetObjectItem(i, "folder");
icculus@58
   524
        printf(" folder uuid: %s\n", json ? json->valuestring : "[none]");
icculus@58
   525
icculus@58
   526
        if ((json = cJSON_GetObjectItem(i, "fave")) != NULL) {
icculus@58
   527
            printf(" fave: %lu\n", (unsigned long) json->valuedouble);
icculus@58
   528
        } else {
icculus@58
   529
            printf(" fave: [no]\n");
icculus@58
   530
        }
icculus@58
   531
icculus@58
   532
        if ((json = cJSON_GetObjectItem(i, "hmac")) != NULL) {
icculus@58
   533
            const char *base64hmac = json->valuestring;
icculus@58
   534
            const int valid = test_item_hmac(i, base64hmac, overviewhmac);
icculus@58
   535
            printf(" hmac: %s [%svalid]\n", base64hmac, valid ? "" : "in");
icculus@58
   536
        } else {
icculus@58
   537
            printf(" hmac: [none]\n");
icculus@58
   538
        }
icculus@58
   539
icculus@58
   540
        // !!! FIXME: lots of code dupe with master key decrypt.
icculus@58
   541
        if ((json = cJSON_GetObjectItem(i, "k")) != NULL) {
icculus@58
   542
            const char *base64key = json->valuestring;
icculus@58
   543
            uint8_t *decoded = NULL;
icculus@58
   544
            const int decodedlen = base64_decode(base64key, -1, &decoded);
icculus@58
   545
            if ((decodedlen != -1) && (decodedlen > 32)) {
icculus@58
   546
                uint8_t digest[32];
icculus@58
   547
                unsigned int digestlen = sizeof (digest);
icculus@58
   548
                if (!HMAC(EVP_sha256(), masterhmac, 32, (const unsigned char *) decoded, decodedlen-digestlen, (unsigned char *) digest, &digestlen)) {
icculus@58
   549
                    fprintf(stderr, " [item key HMAC failed.]\n");
icculus@58
   550
                } else if (digestlen != sizeof (digest)) {
icculus@58
   551
                    fprintf(stderr, "[item key HMAC is wrong size (got=%u expected=%u)].\n", digestlen, (unsigned int) sizeof (digest));
icculus@58
   552
                } else if (memcmp(digest, decoded + (decodedlen-sizeof (digest)), sizeof (digest)) != 0) {
icculus@58
   553
                    fprintf(stderr, "[item key HMAC verification failed.]\n");
icculus@58
   554
                } else {  // HMAC cleared.
icculus@58
   555
                    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
icculus@58
   556
                    const unsigned char *iv = (unsigned char *) (decoded);
icculus@58
   557
                    uint8_t *plaintext = NULL;
icculus@58
   558
                    int decryptedlen = 0;
icculus@58
   559
                    int decryptedlen2 = 0;
icculus@58
   560
icculus@58
   561
                    if (!ctx) {
icculus@58
   562
                        fprintf(stderr, "[item key EVP_CIPHER_CTX_new() failed.]\n");
icculus@58
   563
                    } else if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char *) masterkey, iv)) {
icculus@58
   564
                        fprintf(stderr, "[item key EVP_DecryptInit_ex() failed.]\n");
icculus@58
   565
                    } else if (!EVP_CIPHER_CTX_set_padding(ctx, 0)) {
icculus@58
   566
                        fprintf(stderr, "[item key EVP_CIPHER_CTX_set_padding() failed.]\n");
icculus@58
   567
                    } else if ((plaintext = (uint8_t *) malloc(decodedlen)) == NULL) {
icculus@58
   568
                        fprintf(stderr, "[item key Out of memory.]\n");
icculus@58
   569
                    } else if (!EVP_DecryptUpdate(ctx, plaintext, &decryptedlen, decoded + 16, decodedlen - 48)) {
icculus@58
   570
                        fprintf(stderr, "[item key EVP_DecryptUpdate() failed.]\n");
icculus@58
   571
                    } else if (!EVP_DecryptFinal_ex(ctx, plaintext + decryptedlen, &decryptedlen2)) {
icculus@58
   572
                        fprintf(stderr, "[item key EVP_DecryptFinal_ex() failed.]\n");
icculus@58
   573
                    } else if ((decryptedlen + decryptedlen2) != 64) {
icculus@58
   574
                        fprintf(stderr, "[item key is wrong size.]\n");
icculus@58
   575
                    } else {
icculus@58
   576
                        memcpy(itemkey, plaintext, 32);
icculus@58
   577
                        memcpy(itemhmac, plaintext + 32, 32);
icculus@58
   578
                        itemkeysokay = 1;
icculus@58
   579
                    }
icculus@58
   580
icculus@58
   581
                    free(plaintext);
icculus@58
   582
icculus@58
   583
                    if (ctx) {
icculus@58
   584
                        EVP_CIPHER_CTX_cleanup(ctx);
icculus@58
   585
                    }
icculus@58
   586
                }
icculus@58
   587
            }
icculus@58
   588
            free(decoded);
icculus@58
   589
        }
icculus@58
   590
icculus@58
   591
        if ((json = cJSON_GetObjectItem(i, "o")) != NULL) {
icculus@58
   592
            uint8_t *decrypted = NULL;
icculus@58
   593
            int decryptedlen = 0;
icculus@58
   594
            if (decrypt_opdata_base64("o", json->valuestring, overviewkey, overviewhmac, &decrypted, &decryptedlen)) {
icculus@58
   595
                decrypted[decryptedlen] = 0;
icculus@58
   596
                printf(" o: %s\n", decrypted);
icculus@58
   597
                free(decrypted);
icculus@58
   598
            } else {
icculus@58
   599
                printf(" o: [failed to decrypt]\n");
icculus@58
   600
            }
icculus@58
   601
        }
icculus@58
   602
icculus@58
   603
        if ((json = cJSON_GetObjectItem(i, "d")) != NULL) {
icculus@58
   604
            uint8_t *decrypted = NULL;
icculus@58
   605
            int decryptedlen = 0;
icculus@58
   606
            if (itemkeysokay && decrypt_opdata_base64("d", json->valuestring, itemkey, itemhmac, &decrypted, &decryptedlen)) {
icculus@58
   607
                decrypted[decryptedlen] = 0;
icculus@58
   608
                printf(" d: %s\n", decrypted);
icculus@58
   609
                free(decrypted);
icculus@58
   610
            } else {
icculus@58
   611
                printf(" d: [failed to decrypt]\n");
icculus@58
   612
            }
icculus@58
   613
        }
icculus@58
   614
icculus@58
   615
        printf("\n");
icculus@58
   616
    }
icculus@58
   617
icculus@58
   618
    printf("\n");
icculus@58
   619
}
icculus@58
   620
icculus@58
   621
static void dump_bands(const uint8_t *masterkey, const uint8_t *masterhmac, const uint8_t *overviewkey, const uint8_t *overviewhmac)
icculus@58
   622
{
icculus@58
   623
    for (unsigned int i = 0; i < 16; i++) {
icculus@58
   624
        char fname[16];
icculus@58
   625
        snprintf(fname, sizeof (fname), "band_%X.js", i);
icculus@58
   626
        cJSON *band = load_json(fname);
icculus@58
   627
        if (band) {
icculus@58
   628
            printf("\nBand %s...\n\n", fname);
icculus@58
   629
            dump_band(band, masterkey, masterhmac, overviewkey, overviewhmac);
icculus@58
   630
            cJSON_Delete(band);
icculus@58
   631
        }
icculus@58
   632
    }
icculus@58
   633
}
icculus@58
   634
icculus@58
   635
int main(int argc, char **argv)
icculus@58
   636
{
icculus@58
   637
    OpenSSL_add_all_algorithms();
icculus@58
   638
    ERR_load_crypto_strings();
icculus@58
   639
    OPENSSL_config(NULL);
icculus@58
   640
icculus@58
   641
    if (argc != 3) {
icculus@58
   642
        fprintf(stderr, "\n\nUSAGE: %s </path/to/1Password.opvault> <keychain password>\n\n", argv[0]);
icculus@58
   643
        return 2;
icculus@58
   644
    }
icculus@58
   645
icculus@58
   646
    const char *opvaultpath = argv[1];
icculus@58
   647
    const char *password = argv[2];
icculus@58
   648
icculus@58
   649
    if (chdir(opvaultpath) == -1) {
icculus@58
   650
        fprintf(stderr, "chdir(\"%s\") failed: %s\n", opvaultpath, strerror(errno));
icculus@58
   651
        return 1;
icculus@58
   652
    } else if (chdir("default") == -1) {
icculus@58
   653
        fprintf(stderr, "chdir(\"%s/default\") failed: %s\n", opvaultpath, strerror(errno));
icculus@58
   654
        return 1;
icculus@58
   655
    }
icculus@58
   656
icculus@58
   657
    cJSON *profile = load_json("profile.js");
icculus@58
   658
    if (!profile) {
icculus@58
   659
        fprintf(stderr, "load_json(\"profile.js\") failed.\n");
icculus@58
   660
        return 1;
icculus@58
   661
    }
icculus@58
   662
icculus@58
   663
    printf("profile : "); dump_json(profile); printf("\n");
icculus@58
   664
icculus@58
   665
    uint8_t masterkey[32];
icculus@58
   666
    uint8_t masterhmac[32];
icculus@58
   667
    uint8_t overviewkey[32];
icculus@58
   668
    uint8_t overviewhmac[32];
icculus@58
   669
    if (!prepare_keys(profile, password, masterkey, masterhmac, overviewkey, overviewhmac)) {
icculus@58
   670
        return 1;
icculus@58
   671
    }
icculus@58
   672
icculus@58
   673
    cJSON_Delete(profile);
icculus@58
   674
icculus@58
   675
    dump_folders(overviewkey, overviewhmac);
icculus@58
   676
    dump_bands(masterkey, masterhmac, overviewkey, overviewhmac);
icculus@58
   677
icculus@58
   678
    return 0;
icculus@58
   679
}
icculus@58
   680