diff options
| author | Franklin Wei <frankhwei536@gmail.com> | 2016-08-12 22:25:41 -0400 |
|---|---|---|
| committer | Franklin Wei <frankhwei536@gmail.com> | 2016-08-12 22:25:41 -0400 |
| commit | 4f4ea9958d4d685ac8eef727aa2b294a3e3f9aaa (patch) | |
| tree | 9cb1e21230283f3c3512533e790c90f37f16a6f8 /apps/plugins | |
| parent | 00a489178c5d36a472574a21bec8fbb55fffb9f4 (diff) | |
| download | rockbox-4f4ea9958d4d685ac8eef727aa2b294a3e3f9aaa.zip rockbox-4f4ea9958d4d685ac8eef727aa2b294a3e3f9aaa.tar.gz rockbox-4f4ea9958d4d685ac8eef727aa2b294a3e3f9aaa.tar.bz2 rockbox-4f4ea9958d4d685ac8eef727aa2b294a3e3f9aaa.tar.xz | |
encrypted export, etc.
Change-Id: I0bde425127f57cb403861a10f2dfbf4116439c7f
Diffstat (limited to 'apps/plugins')
| -rw-r--r-- | apps/plugins/passmgr/passmgr.c | 222 |
1 files changed, 196 insertions, 26 deletions
diff --git a/apps/plugins/passmgr/passmgr.c b/apps/plugins/passmgr/passmgr.c index 0054ee3..dc475d1 100644 --- a/apps/plugins/passmgr/passmgr.c +++ b/apps/plugins/passmgr/passmgr.c @@ -41,9 +41,10 @@ /* these can be changed without breaking anything */ #define PASS_MAX 128 -#define KDF_MIN 5000 /* minimum KDF iterations */ +#define KDF_MIN 5000 /* minimum KDF iterations */ #define KDF_MAX 2500000 #define KDF_DEFAULT (HZ / 4) /* decryption will take about this long by default */ +#define KDF_EXPORT 50000 /* encrypted exports use these many iterations */ /* convenience macros */ #define MAX(a, b) (((a)>(b))?(a):(b)) @@ -57,24 +58,26 @@ #endif #define DICEWARE_BUF (16 * DICEWARE_WORDS + 1) +/* this numbering maintans some backwards compatibility: older + * versions had a bool that would be false (zero) for HOTP + * accounts, but gcc would pad it to 4 bytes; by using this + * numbering very little additional logic is needed */ +enum { TYPE_HOTP = 0, TYPE_TOTP = 1, TYPE_STATIC = 3}; + struct account_t { char name[MAX_NAME]; - /* this numbering maintans some backwards compatibility: older - * versions had a bool that would be false (zero) for HOTP - * accounts, but gcc would pad it to 4 bytes; by using this - * numbering very little additional logic is needed */ - enum { TYPE_HOTP = 0, TYPE_TOTP = 1, TYPE_STATIC = 3} type; + int32_t type; union { uint64_t hotp_counter; - int totp_period; + int32_t totp_period; }; - int digits; + uint32_t digits; unsigned char secret[SECRET_MAX]; - int sec_len; + uint32_t sec_len; }; /* in plugin buffer */ @@ -90,7 +93,7 @@ static int kdf_iters = 0; // calculated on first run static char encrypted = 0; // 0 = off, 1 = password, 2 = diceware static char enc_password[PASS_MAX + 1]; // encryption password -static char data_buf[MAX(MAX_NAME, MAX(SECRET_MAX * 2, MAX(20, MAX(URI_MAX, MAX(sizeof(struct account_t), DICEWARE_BUF)))))]; +static char data_buf[MAX(MAX_NAME, MAX(SECRET_MAX * 2, MAX(20, MAX(URI_MAX, MAX(sizeof(struct account_t), MAX(DICEWARE_BUF, PASS_MAX))))))]; static char temp_sec[SECRET_MAX]; static long background_stack[2 * DEFAULT_STACK_SIZE / sizeof(long)]; @@ -259,9 +262,9 @@ static char choose_letter(char *prefix, char selected, char *fmt, ...) rb->gui_synclist_init(&list, &charlist_cb, NULL, false, 1, NULL); rb->gui_synclist_set_icon_callback(&list, NULL); - rb->gui_synclist_select_item(&list, 0); int n_items = 0; + int to_select = 0; for(char c = 'a'; c <= 'z'; ++c) { char temp[8]; @@ -270,12 +273,13 @@ static char choose_letter(char *prefix, char selected, char *fmt, ...) { rb->strlcpy(charlist_items[n_items], temp, sizeof(charlist_items[n_items])); if(selected == c) - rb->gui_synclist_select_item(&list, n_items); + to_select = n_items; ++n_items; } } rb->gui_synclist_set_nb_items(&list, n_items); rb->gui_synclist_limit_scroll(&list, false); + rb->gui_synclist_select_item(&list, to_select); bool done = false; rb->gui_synclist_set_title(&list, str, NOICON); @@ -534,7 +538,8 @@ static bool browse( char *dst, int dst_size, const char *start ) #if 0 /* check an entered password/diceware passphrase with the actual * one */ -/* used as a very weak security measure */ +/* used as a very weak security measure, and disabled because it + * doesn't really do anything */ static bool verify_password(void) { char temp_pass[PASS_MAX]; @@ -996,7 +1001,11 @@ static void save_accts(void) struct aes_ctr_ctx aes_ctx; aes_ctr_init(&aes_ctx, key, nonce); + /* note that in calculating the HMAC we don't use the primary + key, but instead another part of the PBKDF2 output */ + struct hmac_ctx hmac_ctx; + /* 16 bytes are used for the encryption key, 4 for verification */ hmac_sha1_init(&hmac_ctx, key + 16, sizeof(key) - 16); for(int i = 0; i < next_slot; ++i) @@ -1018,7 +1027,10 @@ static void save_accts(void) rb->lseek(fd, mac_offs, SEEK_SET); rb->write(fd, mac, 20); + /* cleanup */ aes_ctr_destroy(&aes_ctx); + wipe_buf(key, sizeof(key)); + wipe_buf(tmp, sizeof(tmp)); } else for(int i = 0; i < next_slot; ++i) @@ -1094,6 +1106,10 @@ static void gather_entropy(void *out, size_t len) timestamp ^= get_utc(); #endif +#ifdef USEC_TIMER + timestamp ^= SWAP32_CONST(USEC_TIMER); +#endif + /* finally mix it all up with an HMAC */ hmac_sha1(×tamp, sizeof(long), ptr, 20, ptr); rb->yield(); @@ -1105,6 +1121,7 @@ static void gather_entropy(void *out, size_t len) char tmp[sizeof(salt) + 4]; PBKDF2(buf, 20, &salt, sizeof(salt), 1, out, len, tmp); + /* clear any errant button presses */ rb->button_clear_queue(); } @@ -1594,13 +1611,13 @@ static void show_code(int acct) switch(accounts[acct].type) { case TYPE_HOTP: - rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd", accounts[acct].digits); + rb->snprintf(format_buf, sizeof(format_buf), "%%0%" PRIu32 "d", accounts[acct].digits); rb->splashf(0, format_buf, next_code(acct)); background_save(); break; #if CONFIG_RTC case TYPE_TOTP: - rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd (%%ld second(s) left)", accounts[acct].digits); + rb->snprintf(format_buf, sizeof(format_buf), "%%0%" PRIu32 "d (%%ld second(s) left)", accounts[acct].digits); rb->splashf(0, format_buf, next_code(acct), accounts[acct].totp_period - get_utc() % accounts[acct].totp_period); break; @@ -1752,6 +1769,9 @@ static void edit_menu(int acct) type_change: + /* don't want to corrupt a save */ + rb->mutex_lock(&save_mutex); + switch(accounts[acct].type) { case TYPE_HOTP: @@ -1767,9 +1787,6 @@ type_change: break; } - /* don't want to corrupt a save */ - rb->mutex_lock(&save_mutex); - while(!quit) { switch(rb->do_menu(menu, &sel, NULL, false)) @@ -1808,7 +1825,7 @@ type_change: rb->snprintf(data_buf, sizeof(data_buf), "%u", (unsigned int) accounts[acct].hotp_counter); break; case TYPE_TOTP: - rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].totp_period); + rb->snprintf(data_buf, sizeof(data_buf), "%" PRIi32, accounts[acct].totp_period); break; case TYPE_STATIC: switch(static_password_menu(accounts[next_slot].secret, SECRET_MAX)) @@ -1855,7 +1872,7 @@ type_change: } else { - rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].digits); + rb->snprintf(data_buf, sizeof(data_buf), "%" PRIu32, accounts[acct].digits); if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0) break; @@ -2118,11 +2135,11 @@ static void export_uri_list(int typemask) case TYPE_TOTP: case TYPE_HOTP: base32_encode(accounts[i].secret, accounts[i].sec_len, buf, sizeof(buf)); - rb->fdprintf(fd, "otpauth://%s/%s?secret=%s&digits=%d", accounts[i].type == TYPE_TOTP ? "totp" : "hotp", + rb->fdprintf(fd, "otpauth://%s/%s?secret=%s&digits=%" PRIu32, accounts[i].type == TYPE_TOTP ? "totp" : "hotp", accounts[i].name, buf, accounts[i].digits); if(accounts[i].type == TYPE_TOTP) - rb->fdprintf(fd, "&period=%d", accounts[i].totp_period); + rb->fdprintf(fd, "&period=%" PRIi32, accounts[i].totp_period); else rb->fdprintf(fd, "&counter=%u", (unsigned) accounts[i].hotp_counter); rb->fdprintf(fd, "\n"); @@ -2139,9 +2156,160 @@ static void export_uri_list(int typemask) rb->splash(HZ, "Success."); } +static void export_encrypted(void) +{ + if(encrypted) + { + MENUITEM_STRINGLIST(menu, "Password to Use", NULL, + "Current Password/Passphrase", + "Enter New Password", + "Cancel"); + bool quit = false; + while(!quit) + { + switch(rb->do_menu(&menu, NULL, NULL, false)) + { + case 0: + rb->strlcpy(data_buf, enc_password, PASS_MAX); + goto export; + case 1: + quit = true; + break; + case 2: + default: + return; + } + } + } + + /* we need to read a password */ + rb->splash(HZ, "Enter encryption password:"); + data_buf[0] = '\0'; + if(rb->kbd_input(data_buf, PASS_MAX) < 0) + return; + + char temp_pass[PASS_MAX]; + + rb->splash(HZ, "Re-enter encryption password:"); + temp_pass[0] = '\0'; + if(rb->kbd_input(temp_pass, PASS_MAX) < 0) + return; + + if(rb->strcmp(temp_pass, data_buf)) + { + rb->splash(HZ, "Passwords do not match!"); + return; + } + + char fname[MAX_PATH]; + +export: + + rb->snprintf(fname, sizeof(fname), "/"); + rb->splash(HZ, "Enter output filename:"); + if(rb->kbd_input(fname, sizeof(fname)) < 0) + return; + + if(rb->file_exists(fname)) + { + rb->splash(HZ, "File already exists!"); + return; + } + + /* begin saving */ + + rb->splash(0, "Exporting..."); + + /* this format essentially mirrors that of the default save + * format, but with some slight modifications: */ + /* all values are stored big-endian */ + /* no hardware AES core is used */ + /* a constant 50,000 PBKDF2 iterations are used */ + + int fd = rb->open(fname, O_WRONLY | O_CREAT | O_TRUNC); + if(fd < 0) + { + rb->splash(HZ, "Couldn't open file."); + return; + } + + rb->mutex_lock(&save_mutex); + + rb->fdprintf(fd, "OTPX"); + + /* generate/write the nonce */ + uint64_t nonce = *rb->current_tick; +#if CONFIG_RTC + nonce |= (uint64_t)get_utc() << 32; +#endif + + rb->write(fd, &nonce, sizeof(nonce)); + + /* placeholder for the MAC */ + off_t mac_offs = rb->lseek(fd, 0, SEEK_CUR); + rb->memset(data_buf, 0, 20); + rb->write(fd, data_buf, 20); + + char key[20]; + char tmp[sizeof(nonce) + 4]; + + PBKDF2(data_buf, rb->strlen(data_buf), &nonce, sizeof(nonce), + KDF_EXPORT, key, sizeof(key), tmp); + + struct aes_ctr_ctx aes_ctx; + aes_ctr_init(&aes_ctx, key, nonce); + + /* note that in calculating the HMAC we don't use the primary + key, but instead another part of the PBKDF2 output */ + + struct hmac_ctx hmac_ctx; + /* 16 bytes are used for the encryption key, 4 for verification */ + hmac_sha1_init(&hmac_ctx, key + 16, sizeof(key) - 16); + + for(int i = 0; i < next_slot; ++i) + { + /* we don't need data_buf to store the password anymore */ + rb->memcpy(data_buf, accounts + i, sizeof(struct account_t)); + struct account_t *acct = (struct account_t*)data_buf; + + /* change endianness */ + acct->type = htobe32(acct->type); + acct->hotp_counter = htobe64(acct->hotp_counter); + acct->digits = htobe32(acct->digits); + acct->sec_len = htobe32(acct->sec_len); + + /* encrypt */ + aes_ctr_process(&aes_ctx, data_buf, data_buf, sizeof(struct account_t)); + + /* write */ + rb->write(fd, data_buf, sizeof(struct account_t)); + + /* and MAC */ + hmac_sha1_process_bytes(&hmac_ctx, data_buf, sizeof(struct account_t)); + } + + /* write the MAC */ + char mac[20]; + + hmac_sha1_finish_ctx(&hmac_ctx, mac); + + rb->lseek(fd, mac_offs, SEEK_SET); + rb->write(fd, mac, 20); + + /* cleanup */ + aes_ctr_destroy(&aes_ctx); + wipe_buf(key, sizeof(key)); + wipe_buf(tmp, sizeof(tmp)); + wipe_buf(temp_pass, sizeof(temp_pass)); + + rb->close(fd); + rb->mutex_unlock(&save_mutex); +} + static void export_menu(void) { MENUITEM_STRINGLIST(menu, "Export Accounts", NULL, + "To Encrypted Backup (all accounts)", "To URI List (static passwords interleaved)", "To URI List (only OTP accounts)", "To 'username:password List' (only static passwords)", @@ -2156,13 +2324,15 @@ static void export_menu(void) switch(rb->do_menu(&menu, &sel, NULL, false)) { case 0: + export_encrypted(); + case 1: export_uri_list(SAVE_ALL); break; - case 1: + case 2: export_uri_list(SAVE_OTP); break; - case 2: - export_uri_list(SAVE_STATIC); + case 3: + export_uri_list(SAVE_STATIC); break; default: quit = true; @@ -2944,7 +3114,7 @@ static void type_code(int acct) /* hackery to get around the lack of %*d support */ char fmt_buf[64], buf[64]; - rb->snprintf(fmt_buf, sizeof(fmt_buf), "%%0%dd", accounts[acct].digits); + rb->snprintf(fmt_buf, sizeof(fmt_buf), "%%0%" PRIu32 "d", accounts[acct].digits); rb->snprintf(buf, sizeof(buf), fmt_buf, code); char *ptr = buf; |