diff options
| author | Franklin Wei <frankhwei536@gmail.com> | 2016-07-16 16:00:52 -0400 |
|---|---|---|
| committer | Franklin Wei <frankhwei536@gmail.com> | 2016-07-16 16:00:52 -0400 |
| commit | 810a3fe537348ad6c862e5310be3bb241b554e42 (patch) | |
| tree | d66ec3455e792bd5522ff507f578fdfa94538743 /apps/plugins/passmgr | |
| parent | 669059efcdfd6313e81ebb8d1bb5cb028b5aa9d6 (diff) | |
| download | rockbox-810a3fe537348ad6c862e5310be3bb241b554e42.zip rockbox-810a3fe537348ad6c862e5310be3bb241b554e42.tar.gz rockbox-810a3fe537348ad6c862e5310be3bb241b554e42.tar.bz2 rockbox-810a3fe537348ad6c862e5310be3bb241b554e42.tar.xz | |
modularize passmgr
Change-Id: I3ce90b6035c3e13cb19f97b737f5bdea2721f830
Diffstat (limited to 'apps/plugins/passmgr')
| -rw-r--r-- | apps/plugins/passmgr/passmgr.c | 2594 | ||||
| -rw-r--r-- | apps/plugins/passmgr/passmgr.make | 27 | ||||
| -rw-r--r-- | apps/plugins/passmgr/wordlist.c | 3 | ||||
| -rw-r--r-- | apps/plugins/passmgr/wordlist.h | 2 |
4 files changed, 2626 insertions, 0 deletions
diff --git a/apps/plugins/passmgr/passmgr.c b/apps/plugins/passmgr/passmgr.c new file mode 100644 index 0000000..1705077 --- /dev/null +++ b/apps/plugins/passmgr/passmgr.c @@ -0,0 +1,2594 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2016 Franklin Wei + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* password manager plugin, supports both one-time static passwords */ + +/* see RFC 4226 and 6238 about the OTP algorithm */ + +#include "plugin.h" + +#include "lib/aes.h" +#include "lib/display_text.h" +#include "lib/pluginlib_actions.h" +#include "lib/pluginlib_exit.h" +#include "lib/sha1.h" + +/* don't change these if you want to maintain backwards compatibility */ +#define MAX_NAME 50 +#define SECRET_MAX 256 +#define URI_MAX (MAX_NAME + SECRET_MAX * 2 + 1) +#define ACCT_FILE PLUGIN_APPS_DATA_DIR "/passmgr.dat" + +#define PASS_MAX 64 +#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 MAX(a, b) (((a)>(b))?(a):(b)) + +#define assert(x) (!(x)?assert_fail():0) + +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; + + union { + uint64_t hotp_counter; + int totp_period; + }; + + int digits; + + unsigned char secret[SECRET_MAX]; + int sec_len; +}; + +/* in plugin buffer */ +static struct account_t *accounts = NULL; + +/* global variables */ + +static int max_accts = 0; // dynamic, depends on plugin buffer size +static int next_slot = 0; + +static int time_offs = 0; // in seconds +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, sizeof(struct account_t)))))]; +static char temp_sec[SECRET_MAX]; +static long background_stack[2 * DEFAULT_STACK_SIZE / sizeof(long)]; + +static void wipe_buf(void *ptr, size_t len) +{ + rb->memset(ptr, 0, len); + rb->memset(ptr, 0xff, len); + rb->memset(ptr, 0, len); +} + +static void erase_sensitive_info(void) +{ + wipe_buf(accounts, sizeof(struct account_t) * max_accts); + wipe_buf(enc_password, sizeof(enc_password)); + wipe_buf(temp_sec, sizeof(temp_sec)); + wipe_buf(background_stack, sizeof(background_stack)); +} + +static void acct_menu(char *title, void (*cb)(int acct)); + +static void assert_fail(void) +{ + rb->splashf(HZ * 2, "Assertion failed! REPORT ME!"); + exit(0); +} + +static int HOTP(unsigned char *secret, size_t sec_len, uint64_t ctr, int digits) +{ + ctr = htobe64(ctr); + unsigned char hash[20]; + if(hmac_sha1(secret, sec_len, &ctr, 8, hash)) + { + return -1; + } + + int offs = hash[19] & 0xF; + uint32_t code = (hash[offs] & 0x7F) << 24 | + hash[offs + 1] << 16 | + hash[offs + 2] << 8 | + hash[offs + 3]; + + int mod = 1; + for(int i = 0; i < digits; ++i) + mod *= 10; + + // debug + // rb->splashf(HZ * 5, "HOTP %*s, %llu, %d: %d", sec_len, secret, htobe64(ctr), digits, code % mod); + + return code % mod; +} + +static bool compare_constant_time(volatile const char* p1, volatile const char* p2, size_t n) +{ + volatile char c = 0; + for (size_t i=0; i<n; ++i) + c |= p1[i] ^ p2[i]; + return (c == 0); +} + +#if CONFIG_RTC +static time_t get_utc(void) +{ + return rb->mktime(rb->get_time()) - time_offs; +} + +static int TOTP(unsigned char *secret, size_t sec_len, uint64_t step, int digits) +{ + if(!step) + return -1; + uint64_t tm = get_utc() / step; + return HOTP(secret, sec_len, tm, digits); +} +#endif + +/* search the accounts for a duplicate */ +static bool acct_exists(const char *name) +{ + for(int i = 0; i < next_slot; ++i) + if(!rb->strcmp(accounts[i].name, name)) + return true; + return false; +} + +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +static int base32_decode(uint8_t *result, int bufSize, const uint8_t *encoded) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if (bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +static int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +/*********************************************************************** + * File browser (from rockpaint) + ***********************************************************************/ + +static bool browse( char *dst, int dst_size, const char *start ) +{ + struct browse_context browse; + + rb->browse_context_init(&browse, SHOW_ALL, + BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, + NULL, NOICON, start, NULL); + + browse.buf = dst; + browse.bufsize = dst_size; + + rb->rockbox_browse(&browse); + + return (browse.flags & BROWSE_SELECTED); +} + +/* word lists */ + +/* prompt the user for a word from a list */ +static void choose_word(char *ret, size_t ret_len, const char **list, size_t list_len) +{ + MENUITEM_STRINGLIST(char_menu, "Choose Letter", NULL, + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z",); + char prefix[3]; + +first_letter: + + rb->memset(prefix, 0, sizeof(prefix)); + rb->splash(HZ, "Choose first letter:"); + while(1) + { + int ret = rb->do_menu(char_menu, NULL, NULL, false); + if(ret >= 0) + { + prefix[0] = 'a' + ret; + break; + } + } + + rb->splash(HZ, "Choose second letter:"); + while(1) + { + int ret = rb->do_menu(char_menu, NULL, NULL, false); + if(ret >= 0) + { + prefix[1] = 'a' + ret; + break; + } + else + goto first_letter; + } + + /* do a binary search of the list to find the first word that + * starts with the prefix */ +} + +/* a simple AES128-CTR implementation */ + +struct aes_ctr_ctx { + char key[16]; + union { + char bytes[16]; + uint64_t half[2]; + } counter; + /* one block */ + char keystream[16]; + uint8_t bytes_left; +}; + +static void aes_ctr_init(struct aes_ctr_ctx *ctx, const char *key, uint64_t nonce) +{ +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + rb->memcpy(ctx->key, key, 16); + ctx->counter.half[0] = nonce; + ctx->counter.half[1] = 0; + ctx->bytes_left = 0; +} + +static void aes_ctr_nextblock(struct aes_ctr_ctx *ctx) +{ + AES128_ECB_encrypt((char*)&ctx->counter, ctx->key, ctx->keystream); + ctx->counter.half[1]++; + ctx->bytes_left = 16; +} + +/* should be safe to operate in-place */ +static void aes_ctr_process(struct aes_ctr_ctx *ctx, const unsigned char *in, unsigned char *out, size_t len) +{ + while(len--) + { + if(!ctx->bytes_left) + aes_ctr_nextblock(ctx); + *out++ = *in++ ^ ctx->keystream[16 - ctx->bytes_left--]; + } +} + +static void aes_ctr_destroy(struct aes_ctr_ctx *ctx) +{ + wipe_buf(ctx, sizeof(*ctx)); +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif +} + +/* yield after this many HMAC iterations */ +#define YIELD_INTERVAL 2500 + +/* internal PBKDF function */ +#if CONFIG_CPU == S5L8702 && !defined(SIMULATOR) + +/* hardware-accelerated version */ + +static void PBKDF2_F(const void *pass, size_t passlen, const void *salt, size_t saltlen, + int c, uint32_t blockidx, void *tmp, char *out) +{ + char buf[64 + 20]; + char *last = buf + 64; + + rb->yield(); + + rb->memcpy(tmp, salt, saltlen); + blockidx = htobe32(blockidx); + rb->memcpy(tmp + saltlen, &blockidx, 4); + + hmac_sha1(pass, passlen, tmp, saltlen + 4, last); + rb->memcpy(out, last, 20); + + /* begin micro-optimization :P */ + for(int j = 0; j < c / YIELD_INTERVAL; ++j) + { + int iters = YIELD_INTERVAL; + if(c % YIELD_INTERVAL == 0 && j == c / YIELD_INTERVAL - 1) + iters--; + for(int i = 0; i < iters; ++i) + { + hmac_sha1_hwaccel(pass, passlen, last, 20, last); + + uint32_t *a = (uint32_t*)out; + const uint32_t *b = (const uint32_t*)last; + + /* out ^= last: */ + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + } + rb->yield(); + } + for(int i = 1; i < c % YIELD_INTERVAL; ++i) + { + hmac_sha1_hwaccel(pass, passlen, last, 20, last); + + uint32_t *a = (uint32_t*)out; + const uint32_t *b = (const uint32_t*)last; + + /* out ^= last: */ + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + } + rb->yield(); +} +#else + +/* all-software version */ + +static void PBKDF2_F(const void *pass, size_t passlen, const void *salt, size_t saltlen, + int c, uint32_t blockidx, void *tmp, char *out) +{ + char last[20]; + + rb->yield(); + + rb->memcpy(tmp, salt, saltlen); + blockidx = htobe32(blockidx); + rb->memcpy(tmp + saltlen, &blockidx, 4); + + hmac_sha1(pass, passlen, tmp, saltlen + 4, last); + rb->memcpy(out, last, 20); + + /* begin micro-optimization :P */ + for(int j = 0; j < c / YIELD_INTERVAL; ++j) + { + int iters = YIELD_INTERVAL; + if(c % YIELD_INTERVAL == 0 && j == c / YIELD_INTERVAL - 1) + iters--; + for(int i = 0; i < iters; ++i) + { + hmac_sha1(pass, passlen, last, 20, last); + + uint32_t *a = (uint32_t*)out; + const uint32_t *b = (const uint32_t*)last; + + /* out ^= last: */ + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + } + rb->yield(); + } + for(int i = 1; i < c % YIELD_INTERVAL; ++i) + { + hmac_sha1(pass, passlen, last, 20, last); + + uint32_t *a = (uint32_t*)out; + const uint32_t *b = (const uint32_t*)last; + + /* out ^= last: */ + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + *a++ ^= *b++; + } + rb->yield(); +} +#endif + +/* uses HMAC-SHA-1 as the underlying PRF */ +/* tmp must be at least saltlen + 4 bytes */ +static void PBKDF2(const void *pass, size_t passlen, const void *salt, size_t saltlen, + int c, char *dk, size_t dklen, void *tmp) +{ +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + /* number of blocks */ + unsigned l = dklen / 20; + if(dklen % 20) + l += 1; // round up + + /* amount of left-over bytes in the final block */ + unsigned r = dklen - (l - 1) * 20; + + for(uint32_t i = 1; i < l; ++i) + { + PBKDF2_F(pass, passlen, salt, saltlen, c, i, tmp, dk); + dk += 20; + } + if(r) + { + char temp_block[20]; + PBKDF2_F(pass, passlen, salt, saltlen, c, l, tmp, temp_block); + rb->memcpy(dk, temp_block, r); + } +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif +} + +/* calculate about how many KDF iterations it takes to make key + * derivation take a certain time */ +static int calc_kdf_iters(long delay) +{ + rb->splash(0, "Please wait..."); + int iters = KDF_MIN; + long ticks = 0; +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + while(ticks < 4 && iters < KDF_MAX) + { + char out[20]; + char tmp[4 + 4]; + long start = *rb->current_tick; + PBKDF2("password", 8, "salt", 4, KDF_MIN, out, 20, tmp); + long end = *rb->current_tick; + ticks = end - start; + if(!ticks) + iters *= 2; + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif + + if(!ticks) + return KDF_MAX; + + int ret = (delay * iters) / ticks; + rb->lcd_update(); + return ret < KDF_MIN ? KDF_MIN : ret; +} + +static bool read_accts(void) +{ + int fd = rb->open(ACCT_FILE, O_RDONLY); + if(fd < 0) + return false; + + unsigned char buf[4]; + /* two versions to maintain backwards-compatibility */ + const char *magic_old = "OTP1"; + const char *magic = "OTP2"; + rb->read(fd, buf, 4); + if(rb->memcmp(magic, buf, 4) && rb->memcmp(magic_old, buf, 4)) + { + rb->splash(HZ * 2, "Corrupt save data!"); + rb->close(fd); + return false; + } + + rb->read(fd, &time_offs, sizeof(time_offs)); + + if(!rb->memcmp(magic, buf, 4)) + { + /* version 2 */ + rb->read(fd, &encrypted, sizeof(encrypted)); + rb->read(fd, &kdf_iters, sizeof(kdf_iters)); + + if(encrypted) + { + uint64_t nonce; + rb->read(fd, &nonce, sizeof(nonce)); + + /* read in the MAC */ + char mac_given[20]; + rb->read(fd, mac_given, 20); + + /* also read the encrypted data into memory */ + while(next_slot < max_accts) + { + if(rb->read(fd, accounts + next_slot, sizeof(struct account_t)) != sizeof(struct account_t)) + break; + ++next_slot; + } + + rb->close(fd); + + for(int i = 0; i < 3; ++i) + { + rb->splash(HZ * 2, "Enter password:"); + enc_password[0] = '\0'; + if(rb->kbd_input(enc_password, sizeof(enc_password)) < 0) + { + rb->close(fd); + exit(PLUGIN_ERROR); + } + + rb->splash(0, "Decrypting..."); + + /* derive the key */ + char key[20]; + char tmp[sizeof(nonce) + 4]; + + //long start = *rb->current_tick; + PBKDF2(enc_password, rb->strlen(enc_password), &nonce, sizeof(nonce), + kdf_iters, key, sizeof(key), tmp); + //long end = *rb->current_tick; + //rb->splashf(HZ, "Key derviation takes %ld ticks", end - start); + +#if CONFIG_CPU == S5L8702 && !defined(SIMULATOR) + /* if we have a hardware AES coprocessor with + * device-unique keys, use it to encrypt the key to + * tie it to this device */ + rb->s5l8702_hwkeyaes(HWKEYAES_ENCRYPT, + HWKEYAES_UKEY, + key, sizeof(key)); +#endif + + /* calculate the MAC of the ciphertext to see if the + * password is correct before decrypting note that we + * only use 4 bytes of the derived key in calculating + * the MAC, this makes an attack more difficult and + * prone to false positives, which is good */ + char mac_calculated[20]; + hmac_sha1(key + 16, sizeof(key) - 16, accounts, + next_slot * sizeof(struct account_t), mac_calculated); + + if(!compare_constant_time(mac_calculated, mac_given, 20)) + { + rb->splash(HZ, "Wrong password!"); + continue; + } + + /* decrypt the data with AES128-CTR */ + struct aes_ctr_ctx aes_ctx; + + aes_ctr_init(&aes_ctx, key, nonce); + + aes_ctr_process(&aes_ctx, (const unsigned char*)accounts, (char*)accounts, sizeof(struct account_t) * next_slot); + + aes_ctr_destroy(&aes_ctx); + + /* successful decryption */ + return true; + } + + exit(PLUGIN_ERROR); + } + } + + /* plain, unencrypted format */ + + while(next_slot < max_accts) + { + if(rb->read(fd, accounts + next_slot, sizeof(struct account_t)) != sizeof(struct account_t)) + break; + ++next_slot; + } + + rb->close(fd); + return true; +} + +static struct mutex save_mutex; +static volatile bool quiet_save SHAREDDATA_ATTR = false; + +static void save_accts(void) +{ + if(!quiet_save) + rb->splash(0, "Saving..."); + rb->mutex_lock(&save_mutex); + int fd = rb->open(ACCT_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + rb->fdprintf(fd, "OTP2"); + + rb->write(fd, &time_offs, sizeof(time_offs)); + rb->write(fd, &encrypted, sizeof(encrypted)); + + /* write how many KDF iterations we use even if encryption is disabled */ + rb->write(fd, &kdf_iters, sizeof(kdf_iters)); + + assert(sizeof(data_buf) >= sizeof(struct account_t)); + assert(sizeof(data_buf) >= 20); // needs to hold an SHA-1 hash + + if(encrypted) + { + /* encrypt the data with AES128-CTR */ + + /* 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); + + /* use PKCS #5 PBKDF2 to derive a strong key from the password */ + char key[20]; + char tmp[sizeof(nonce) + 4]; + + PBKDF2(enc_password, rb->strlen(enc_password), &nonce, sizeof(nonce), + kdf_iters, key, sizeof(key), tmp); + +#if CONFIG_CPU == S5L8702 && !defined(SIMULATOR) + /* if we have a hardware AES coprocessor with device-unique + * keys, use it to encrypt the key to tie it to this device */ + rb->s5l8702_hwkeyaes(HWKEYAES_ENCRYPT, + HWKEYAES_UKEY, + key, sizeof(key)); +#endif + + struct aes_ctr_ctx aes_ctx; + aes_ctr_init(&aes_ctx, key, nonce); + + struct hmac_ctx hmac_ctx; + hmac_sha1_init(&hmac_ctx, key + 16, sizeof(key) - 16); + + for(int i = 0; i < next_slot; ++i) + { + /* encrypt */ + aes_ctr_process(&aes_ctx, (unsigned char*)(accounts + i), data_buf, sizeof(struct account_t)); + + rb->write(fd, data_buf, sizeof(struct account_t)); + + /* then MAC */ + hmac_sha1_process_bytes(&hmac_ctx, data_buf, sizeof(struct account_t)); + rb->yield(); + } + + char mac[20]; + + hmac_sha1_finish_ctx(&hmac_ctx, mac); + + rb->lseek(fd, mac_offs, SEEK_SET); + rb->write(fd, mac, 20); + + aes_ctr_destroy(&aes_ctx); + } + else + for(int i = 0; i < next_slot; ++i) + rb->write(fd, accounts + i, sizeof(struct account_t)); + + rb->close(fd); + rb->mutex_unlock(&save_mutex); + quiet_save = false; +} + +static volatile bool kill_background SHAREDDATA_ATTR = false; +static volatile bool want_save SHAREDDATA_ATTR = false; +static int background_id = -1; + +static void background_save(void) +{ + want_save = true; + /* fall back to normal save */ + if(background_id < 0) + save_accts(); +} + +static void background_thread(void) +{ + while(1) + { + if(want_save) + { + quiet_save = true; + save_accts(); + want_save = false; + } + if(kill_background) + rb->thread_exit(); + rb->sleep(HZ / 25); + } +} + +static int compare_acct(const void *a, const void *b) +{ + const struct account_t *a1 = a, *b1 = b; + return rb->strcmp(a1->name, b1->name); +} + +static void sort_accts(void) +{ + /* don't sort while a save is going on */ + rb->mutex_lock(&save_mutex); + rb->qsort(accounts, next_slot, sizeof(struct account_t), compare_acct); + rb->mutex_unlock(&save_mutex); +} + +static void add_acct_file(void) +{ + char fname[MAX_PATH]; + rb->splash(HZ * 2, "Please choose the file that contains the account(s)."); + int before = next_slot; + if(browse(fname, sizeof(fname), "/")) + { + int fd = rb->open(fname, O_RDONLY); + do { + char *uri_buf = data_buf; + rb->memset(accounts + next_slot, 0, sizeof(struct account_t)); + + accounts[next_slot].digits = 6; + + if(!rb->read_line(fd, uri_buf, URI_MAX)) + break; + + if(next_slot >= max_accts) + { + rb->splash(HZ * 2, "Account limit reached: some accounts not added."); + break; + } + + char *save; + + /* check for URI prefix */ + if(rb->strncmp(uri_buf, "otpauth://", 10)) + { + /* see if it could be in the format name:password */ + if(rb->strchr(uri_buf, ':')) + { + char *tok = rb->strtok_r(uri_buf, ":", &save); + + if(acct_exists(tok)) + { + rb->splashf(HZ * 2, "Not adding account with duplicate name `%s'!", tok); + continue; + } + + if(!rb->strlen(tok)) + { + rb->splashf(HZ * 2, "Skipping account with empty name."); + continue; + } + + rb->strlcpy(accounts[next_slot].name, tok, sizeof(accounts[next_slot].name)); + + tok = rb->strtok_r(NULL, "", &save); + if(rb->strlen(tok) >= SECRET_MAX) + rb->splashf(HZ * 2, "Truncating secret for account `%s'", accounts[next_slot].name); + rb->strlcpy(accounts[next_slot].secret, tok, sizeof(accounts[next_slot].secret)); + accounts[next_slot].type = TYPE_STATIC; + ++next_slot; + } + continue; + } + + char *tok = rb->strtok_r(uri_buf + 10, "/", &save); + if(!rb->strcmp(tok, "totp")) + { + accounts[next_slot].type = TYPE_TOTP; + accounts[next_slot].totp_period = 30; +#if !CONFIG_RTC + rb->splash(2 * HZ, "Skipping TOTP account (not supported)."); + continue; +#endif + } + else if(!rb->strcmp(tok, "hotp")) + { + accounts[next_slot].type = TYPE_HOTP; + accounts[next_slot].hotp_counter = 0; + } + + tok = rb->strtok_r(NULL, "?", &save); + if(!tok) + continue; + + if(acct_exists(tok)) + { + rb->splashf(HZ * 2, "Not adding account with duplicate name `%s'!", tok); + continue; + } + + if(!rb->strlen(tok)) + { + rb->splashf(HZ * 2, "Skipping account with empty name."); + continue; + } + + rb->strlcpy(accounts[next_slot].name, tok, sizeof(accounts[next_slot].name)); + + bool have_secret = false; + + do { + tok = rb->strtok_r(NULL, "=", &save); + if(!tok) + continue; + + if(!rb->strcmp(tok, "secret")) + { + if(have_secret) + { + rb->splashf(HZ * 2, "URI with multiple `secret' parameters found, skipping!"); + goto fail; + } + have_secret = true; + tok = rb->strtok_r(NULL, "&", &save); + if((accounts[next_slot].sec_len = base32_decode(accounts[next_slot].secret, SECRET_MAX, tok)) <= 0) + goto fail; + } + else if(!rb->strcmp(tok, "counter")) + { + if(accounts[next_slot].type == TYPE_TOTP) + { + rb->splash(HZ * 2, "Counter parameter specified for TOTP!? Skipping..."); + goto fail; + } + tok = rb->strtok_r(NULL, "&", &save); + accounts[next_slot].hotp_counter = rb->atoi(tok); + } + else if(!rb->strcmp(tok, "period")) + { + if(accounts[next_slot].type == TYPE_HOTP) + { + rb->splash(HZ * 2, "Period parameter specified for HOTP!? Skipping..."); + goto fail; + } + tok = rb->strtok_r(NULL, "&", &save); + accounts[next_slot].totp_period = rb->atoi(tok); + } + else if(!rb->strcmp(tok, "digits")) + { + tok = rb->strtok_r(NULL, "&", &save); + accounts[next_slot].digits = rb->atoi(tok); + if(accounts[next_slot].digits < 1 || accounts[next_slot].digits > 9) + { + rb->splashf(HZ * 2, "Digits parameter not in acceptable range, skipping."); + goto fail; + } + } + else + rb->splashf(HZ, "Unnown parameter `%s' ignored.", tok); + } while(tok); + + if(!have_secret) + { + rb->splashf(HZ * 2, "URI with no `secret' parameter found, skipping!"); + goto fail; + } + + /* wait if a background save is going on */ + rb->mutex_lock(&save_mutex); + + ++next_slot; + + rb->mutex_unlock(&save_mutex); + + fail: + + ; + } while(1); + rb->close(fd); + } + if(before == next_slot) + rb->splash(HZ * 2, "No accounts added."); + else + { + rb->splashf(HZ * 2, "Added %d account(s).", next_slot - before); + sort_accts(); + background_save(); + } +} + +static void add_acct_manual(void) +{ + if(next_slot >= max_accts) + { + rb->splashf(HZ * 2, "Account limit reached!"); + return; + } + rb->memset(accounts + next_slot, 0, sizeof(struct account_t)); + + rb->splash(HZ * 1, "Enter account name:"); + if(rb->kbd_input(accounts[next_slot].name, sizeof(accounts[next_slot].name)) < 0) + return; + + if(acct_exists(accounts[next_slot].name)) + { + rb->splash(HZ * 2, "Duplicate account name!"); + return; + } + + MENUITEM_STRINGLIST(type_menu, "Choose Account Type", NULL, + "HOTP (event-based)", +#if CONFIG_RTC + "TOTP (time-based)", +#endif + "Static password", + "Cancel"); + + switch(rb->do_menu(&type_menu, NULL, NULL, false)) + { + case 0: + accounts[next_slot].type = TYPE_HOTP; + break; + case 1: +#if CONFIG_RTC + accounts[next_slot].type = TYPE_TOTP; +#else + accounts[next_slot].type = TYPE_STATIC; +#endif + break; + case 2: +#if CONFIG_RTC + accounts[next_slot].type = TYPE_STATIC; +#endif + break; + case 3: + default: + case GO_TO_PREVIOUS: + break; + } + + if(accounts[next_slot].type != TYPE_STATIC) + rb->splash(HZ * 2, "Enter Base32-encoded secret:"); + else + rb->splash(HZ * 2, "Enter account password:"); + + char temp_buf[SECRET_MAX * 2]; + rb->memset(temp_buf, 0, sizeof(temp_buf)); + + if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0) + return; + + if(accounts[next_slot].type != TYPE_STATIC) + { + if((accounts[next_slot].sec_len = base32_decode(accounts[next_slot].secret, SECRET_MAX, temp_buf)) <= 0) + { + rb->splash(HZ * 2, "Invalid Base32 secret!"); + return; + } + } + else + { + accounts[next_slot].sec_len = rb->strlen(temp_buf); + if(accounts[next_slot].sec_len > SECRET_MAX) + { + rb->splash(HZ * 2, "Password too long!"); + return; + } + rb->strlcpy(accounts[next_slot].secret, temp_buf, SECRET_MAX); + goto done; + } + + rb->memset(temp_buf, 0, sizeof(temp_buf)); + + if(accounts[next_slot].type == TYPE_HOTP) + { + rb->splash(HZ * 2, "Enter counter (0 is typical):"); + temp_buf[0] = '0'; + } + else if(accounts[next_slot].type == TYPE_TOTP) + { + rb->splash(HZ * 2, "Enter time step (30 is typical):"); + temp_buf[0] = '3'; + temp_buf[1] = '0'; + } + + if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0) + return; + + if(accounts[next_slot].type == TYPE_TOTP) + accounts[next_slot].hotp_counter = rb->atoi(temp_buf); + else + accounts[next_slot].totp_period = rb->atoi(temp_buf); + + rb->splash(HZ * 2, "Enter code length (6 is typical):"); + + rb->memset(temp_buf, 0, sizeof(temp_buf)); + temp_buf[0] = '6'; + + if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0) + return; + + accounts[next_slot].digits = rb->atoi(temp_buf); + + if(accounts[next_slot].digits < 1 || accounts[next_slot].digits > 9) + { + rb->splash(HZ, "Invalid length!"); + return; + } + +done: + + rb->mutex_lock(&save_mutex); + + ++next_slot; + + rb->mutex_unlock(&save_mutex); + + sort_accts(); + background_save(); + + rb->splashf(HZ, "Success."); +} + +static void add_acct(void) +{ + MENUITEM_STRINGLIST(menu, "Import Account(s)", NULL, + "From URI list or 'username:password' list", + "Manual Entry", + "Back"); + int sel = 0; + bool quit = false; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + add_acct_file(); + break; + case 1: + add_acct_manual(); + break; + case 2: + default: + quit = true; + break; + } + } +} + +/* core algorithm, only for OTP accounts */ +static int next_code(int acct) +{ + switch(accounts[acct].type) + { + case TYPE_HOTP: + { + int ret = HOTP(accounts[acct].secret, + accounts[acct].sec_len, + accounts[acct].hotp_counter, + accounts[acct].digits); + rb->mutex_lock(&save_mutex); + ++accounts[acct].hotp_counter; + rb->mutex_unlock(&save_mutex); + return ret; + } +#if CONFIG_RTC + case TYPE_TOTP: + return TOTP(accounts[acct].secret, + accounts[acct].sec_len, + accounts[acct].totp_period, + accounts[acct].digits); +#endif + default: + return -1; + } +} + +static void show_code(int acct) +{ + /* rockbox's printf doesn't support a variable field width afaik */ + char format_buf[64]; + switch(accounts[acct].type) + { + case TYPE_HOTP: + rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd", 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->splashf(0, format_buf, next_code(acct), + accounts[acct].totp_period - get_utc() % accounts[acct].totp_period); + break; +#else + case TYPE_TOTP: + rb->splash(0, "TOTP not supported on this device!"); + break; +#endif + case TYPE_STATIC: + rb->splashf(0, "%s", accounts[acct].secret); + break; + default: + assert(false); + break; + } + rb->sleep(HZ); + while(1) + { + int button = rb->button_get(true); + if(button && !(button & BUTTON_REL)) + break; + rb->yield(); + } + + rb->lcd_update(); +} + +static void gen_codes(void) +{ + acct_menu("Show Password", show_code); +} + +static bool danger_confirm(void) +{ + int sel = 0; + MENUITEM_STRINGLIST(menu, "Are you REALLY SURE?", NULL, + "No", + "No", + "No", + "No", + "No", + "No", + "No", + "Yes, DO IT", // 7 + "No", + "No", + "No", + "No"); + + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 7: + return true; + default: + return false; + } +} + +static void acct_type_menu(int acct) +{ + MENUITEM_STRINGLIST(type_menu, "Choose Account Type", NULL, + "HOTP (event-based)", + "TOTP (time-based)", + "Static password", + "Back"); + int sel = 0; + switch(accounts[acct].type) + { + case TYPE_HOTP: + break; + case TYPE_TOTP: + sel = 1; + break; + case TYPE_STATIC: + sel = 2; + break; + } + + rb->mutex_lock(&save_mutex); + + if(accounts[acct].type != TYPE_STATIC) + base32_encode(accounts[acct].secret, accounts[acct].sec_len, accounts[acct].secret, SECRET_MAX); + + switch(rb->do_menu(&type_menu, &sel, NULL, false)) + { + case 0: + accounts[acct].type = TYPE_HOTP; + break; + case 1: + accounts[acct].type = TYPE_TOTP; + break; + case 2: + accounts[acct].type = TYPE_STATIC; + break; + case 3: + default: + break; + } + + rb->mutex_unlock(&save_mutex); +} + +static void edit_menu(int acct) +{ + /* HACK ALERT */ + /* three different menus, one handling logic */ + MENUITEM_STRINGLIST(menu_hotp, "Edit Account", NULL, + "Rename", // 0 + "Delete", // 1 + "Change HOTP Counter", // 2 + "Change Digit Count", // 3 + "Change Shared Secret", // 4 + "Change Type", // 5 + "Back"); // 6 + + MENUITEM_STRINGLIST(menu_totp, "Edit Account", NULL, + "Rename", // 0 + "Delete", // 1 + "Change TOTP Period", // 2 + "Change Digit Count", // 3 + "Change Shared Secret", // 4 + "Change Type", // 5 + "Back"); // 6 + + MENUITEM_STRINGLIST(menu_static, "Edit Account", NULL, + "Rename", // 0 + "Delete", // 1 + "Change Password", // 2 + "Change Type", // 3 + "Back"); // 4 + + const struct menu_item_ex *menu = NULL; + + bool save = false; + bool quit = false; + int sel = 0; + +type_change: + + switch(accounts[acct].type) + { + case TYPE_HOTP: + menu = &menu_hotp; + break; + case TYPE_TOTP: + menu = &menu_totp; + break; + case TYPE_STATIC: + menu = &menu_static; + break; + default: + break; + } + + /* don't want to corrupt a save */ + rb->mutex_lock(&save_mutex); + + while(!quit) + { + switch(rb->do_menu(menu, &sel, NULL, false)) + { + case 0: // rename + rb->splash(HZ, "Enter new name:"); + rb->strlcpy(data_buf, accounts[acct].name, sizeof(data_buf)); + if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0) + break; + if(acct_exists(data_buf)) + { + rb->splash(HZ * 2, "Duplicate account name!"); + break; + } + rb->strlcpy(accounts[acct].name, data_buf, sizeof(accounts[acct].name)); + sort_accts(); + save = true; + rb->splash(HZ, "Success."); + goto done; + case 1: // delete + if(danger_confirm()) + { + rb->memmove(accounts + acct, accounts + acct + 1, (next_slot - acct - 1) * sizeof(struct account_t)); + --next_slot; + rb->splashf(HZ, "Deleted."); + save = true; + goto done; + } + else + rb->splash(HZ, "Not confirmed."); + break; + case 2: // HOTP counter OR TOTP period or password + switch(accounts[acct].type) + { + case TYPE_HOTP: + 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); + break; + case TYPE_STATIC: + rb->snprintf(data_buf, sizeof(data_buf), "%s", accounts[acct].secret); + break; + } + + if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0) + break; + + switch(accounts[acct].type) + { + case TYPE_TOTP: + accounts[acct].totp_period = rb->atoi(data_buf); + break; + case TYPE_HOTP: + accounts[acct].hotp_counter = rb->atoi(data_buf); + break; + case TYPE_STATIC: + rb->strlcpy(accounts[acct].secret, data_buf, SECRET_MAX); + break; + } + + save = true; + + rb->splash(HZ, "Success."); + break; + case 3: // digits or type + if(accounts[acct].type == TYPE_STATIC) + { + acct_type_menu(acct); + save = true; + rb->mutex_unlock(&save_mutex); + goto type_change; + } + else + { + rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].digits); + if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0) + break; + + accounts[acct].digits = rb->atoi(data_buf); + + save = true; + + rb->splash(HZ, "Success."); + } + break; + case 4: // secret or back + { + if(accounts[acct].type == TYPE_STATIC) + { + quit = true; + break; + } + /* save the old secret */ + size_t old_len = accounts[acct].sec_len; + rb->memcpy(temp_sec, accounts[acct].secret, accounts[acct].sec_len); + + /* encode */ + base32_encode(accounts[acct].secret, accounts[acct].sec_len, data_buf, sizeof(data_buf)); + + if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0) + break; + + int ret = base32_decode(accounts[acct].secret, sizeof(accounts[acct].secret), data_buf); + if(ret <= 0) + { + rb->memcpy(accounts[acct].secret, temp_sec, SECRET_MAX); + accounts[acct].sec_len = old_len; + rb->splash(HZ * 2, "Invalid Base32 secret!"); + break; + } + accounts[acct].sec_len = ret; + + save = true; + + rb->splash(HZ, "Success."); + + break; + } + case 5: + acct_type_menu(acct); + save = true; + rb->mutex_unlock(&save_mutex); + goto type_change; + case 6: + quit = true; + break; + default: + break; + } + } +done: + + /* done modifying */ + rb->mutex_unlock(&save_mutex); + + if(save) + background_save(); +} + +static void edit_accts(void) +{ + acct_menu("Edit Account", edit_menu); +} + +#if CONFIG_RTC +/* label is like this: UTC([+/-]HH:MM ...) */ +static int get_time_seconds(const char *label) +{ + if(!rb->strcmp(label, "UTC")) + return 0; + + char buf[32]; + + /* copy the part after "UTC" */ + rb->strlcpy(buf, label + 3, sizeof(buf)); + + char *save, *tok; + + tok = rb->strtok_r(buf, ":", &save); + /* positive or negative: sign left */ + int hr = rb->atoi(tok); + + tok = rb->strtok_r(NULL, ": ", &save); + int min = rb->atoi(tok); + + return 3600 * hr + 60 * min; +} + +/* returns the offset in seconds associated with a time zone */ +static int get_time_offs(void) +{ + MENUITEM_STRINGLIST(menu, "Select Time Zone", NULL, + "UTC-12:00", // 0 + "UTC-11:00", // 1 + "UTC-10:00 (HAST)", // 2 + "UTC-9:30", // 3 + "UTC-9:00 (AKST, HADT)", // 4 + "UTC-8:00 (PST, AKDT)", // 5 + "UTC-7:00 (MST, PDT)", // 6 + "UTC-6:00 (CST, MDT)", // 7 + "UTC-5:00 (EST, CDT)", // 8 + "UTC-4:00 (AST, EDT)", // 9 + "UTC-3:30 (NST)", // 10 + "UTC-3:00 (ADT)", // 11 + "UTC-2:30 (NDT)", // 12 + "UTC-2:00", // 13 + "UTC-1:00", // 14 + "UTC", // 15 + "UTC+1:00", // 16 + "UTC+2:00", // 17 + "UTC+3:00", // 18 + "UTC+3:30", // 19 + "UTC+4:00", // 20 + "UTC+4:30", // 21 + "UTC+5:00", // 22 + "UTC+5:30", // 23 + "UTC+5:45", // 24 + "UTC+6:00", // 25 + "UTC+6:30", // 26 + "UTC+7:00", // 27 + "UTC+8:00", // 28 + "UTC+8:30", // 29 + "UTC+8:45", // 30 + "UTC+9:00", // 31 + "UTC+9:30", // 32 + "UTC+10:00", // 33 + "UTC+10:30", // 34 + "UTC+11:00", // 35 + "UTC+12:00", // 36 + "UTC+12:45", // 37 + "UTC+13:00", // 38 + "UTC+14:00", // 39 + ); + + int sel = 15; // UTC + for(unsigned int i = 0; i < ARRAYLEN(menu_); ++i) + if(time_offs == get_time_seconds(menu_[i])) + { + sel = i; + break; + } + + rb->do_menu(&menu, &sel, NULL, false); + + if(0 <= sel && sel < (int)ARRAYLEN(menu_)) + { + /* see apps/menu.h */ + const char *label = menu_[sel]; + + return get_time_seconds(label); + } + else + return time_offs; + +#if 0 + /* kept just in case menu internals change and the above code + * breaks */ + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: case 1: case 2: + return (sel - 12) * 3600; + case 3: + return -9 * 3600 - 30 * 60; + case 4: case 5: case 6: case 7: case 8: case 9: + return (sel - 13) * 3600; + case 10: + return -3 * 3600 - 30 * 60; + case 11: + return -3 * 3600; + case 12: + return -3 * 3600 - 30 * 60; + case 13: case 14: case 15: case 16: case 17: case 18: + return (sel - 15) * 3600; + + case 19: + return 3 * 3600 + 30 * 60; + case 20: + return 4 * 3600; + case 21: + return 4 * 3600 + 30 * 60; + case 22: + return 5 * 3600; + case 23: + return 5 * 3600 + 30 * 60; + case 24: + return 5 * 3600 + 45 * 60; + case 25: + return 6 * 3600; + case 26: + return 6 * 3600 + 30 * 60; + case 27: case 28: + return (sel - 20) * 3600; + case 29: + return 8 * 3600 + 30 * 60; + case 30: + return 8 * 3600 + 45 * 60; + case 31: + return 9 * 3600; + case 32: + return 9 * 3600 + 30 * 60; + case 33: + return 10 * 3600; + case 34: + return 10 * 3600 + 30 * 60; + case 35: case 36: + return (sel - 24) * 3600; + case 37: + return 12 * 3600 + 45 * 60; + case 38: case 39: + return (sel - 25) * 3600; + default: + rb->splash(0, "BUG: time zone fall-through: REPORT ME!!!"); + break; + } + return 0; +#endif +} +#endif + +#define SAVE_HOTP (1<<0) +#define SAVE_TOTP (1<<1) +#define SAVE_STATIC (1<<2) +#define SAVE_OTP (SAVE_HOTP | SAVE_TOTP) +#define SAVE_ALL (SAVE_OTP | SAVE_STATIC) + +static void export_uri_list(int typemask) +{ + static char buf[MAX(MAX_PATH, SECRET_MAX * 2)]; + buf[0] = '/'; + buf[1] = '\0'; + rb->splash(HZ * 2, "Enter output filename:"); + if(rb->kbd_input(buf, sizeof(buf)) < 0) + return; + + if(rb->file_exists(buf)) + { + rb->splash(HZ, "File already exists!"); + return; + } + + int fd = rb->open(buf, O_WRONLY | O_CREAT | O_TRUNC); + if(fd < 0) + { + rb->splashf(HZ, "Couldn't open file."); + return; + } + + for(int i = 0; i < next_slot ; ++i) + { + if((accounts[i].type + 1) & typemask) + { + switch(accounts[i].type) + { + 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", + accounts[i].name, buf, accounts[i].digits); + + if(accounts[i].type == TYPE_TOTP) + rb->fdprintf(fd, "&period=%d", accounts[i].totp_period); + else + rb->fdprintf(fd, "&counter=%u", (unsigned) accounts[i].hotp_counter); + rb->fdprintf(fd, "\n"); + break; + case TYPE_STATIC: + rb->fdprintf(fd, "%s:%s\n", accounts[i].name, accounts[i].secret); + break; + } + } + } + + rb->close(fd); + + rb->splash(HZ, "Success."); +} + +static void export_menu(void) +{ + MENUITEM_STRINGLIST(menu, "Export Accounts", NULL, + "To URI list (static passwords interleaved)", + "To URI list (only OTP accounts)", + "To 'username:password list' (only static passwords)", + "Back"); + + int sel = 0; + + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + export_uri_list(SAVE_ALL); + break; + case 1: + export_uri_list(SAVE_OTP); + break; + case 2: + export_uri_list(SAVE_STATIC); + break; + default: + break; + } +} + +static void kdf_delay_menu(void) +{ + MENUITEM_STRINGLIST(menu, "Change KDF Delay", NULL, + "50 ms -- fastest, least secure", // 0 + "100 ms", // 1 + "250 ms -- default", // 2 + "350 ms", // 3 + "500 ms", // 4 + "750 ms", // 5 + "1000 ms", // 6 + "1500 ms", // 7 + "2500 ms -- for the extremely paranoid", // 8 + "Back"); + int ticks = 0; + while(!ticks) + { + switch(rb->do_menu(&menu, NULL, NULL, false)) + { + case 0: + ticks = 5 * HZ / 100; + break; + case 1: + ticks = 10 * HZ / 100; + break; + case 2: + ticks = 25 * HZ / 100; + break; + case 3: + ticks = 35 * HZ / 100; + break; + case 4: + ticks = 50 * HZ / 100; + break; + case 5: + ticks = 75 * HZ / 100; + break; + case 6: + ticks = 100 * HZ / 100; + break; + case 7: + ticks = 150 * HZ / 100; + break; + case 8: + ticks = 250 * HZ / 100; + break; + default: + return; + } + } + if(ticks) + { + rb->mutex_lock(&save_mutex); + kdf_iters = calc_kdf_iters(ticks); + rb->mutex_unlock(&save_mutex); + background_save(); + } + rb->splashf(HZ, "Using %d PBKDF2 iterations", kdf_iters); +} + +static void encrypt_menu(void) +{ + MENUITEM_STRINGLIST(encrypt_menu_1, "Encryption", NULL, + "Change Password", + "Use a Passphrase", + "Change KDF Delay", + "Disable", + "Back"); + + MENUITEM_STRINGLIST(encrypt_menu_2, "Encryption", NULL, + "Enable", + "Back"); + + const struct menu_item_ex *menu = encrypted ? &encrypt_menu_1 : &encrypt_menu_2; + + switch(rb->do_menu(menu, NULL, NULL, false)) + { + case 0: + { + char temp_pass[sizeof(enc_password)]; + char temp_pass2[sizeof(enc_password)]; + + temp_pass[0] = '\0'; + + if(encrypted) + { + rb->splash(HZ * 2, "Enter current password:"); + + if(rb->kbd_input(temp_pass, sizeof(temp_pass)) < 0) + break; + + if(rb->strcmp(enc_password, temp_pass)) + { + rb->splashf(HZ * 2, "Wrong password!"); + break; + } + + temp_pass[0] = '\0'; + } + + rb->splash(HZ * 2, "Enter new password:"); + + if(rb->kbd_input(temp_pass, sizeof(temp_pass)) < 0) + break; + + temp_pass2[0] = '\0'; + + rb->splash(HZ * 2, "Re-enter new password:"); + + if(rb->kbd_input(temp_pass2, sizeof(temp_pass2)) < 0) + break; + + if(rb->strcmp(temp_pass, temp_pass2)) + { + rb->splash(HZ * 2, "Passwords do not match!"); + break; + } + + rb->strlcpy(enc_password, temp_pass, sizeof(enc_password)); + + encrypted = 1; + + background_save(); + + rb->splash(HZ, "Success."); + break; + } + case 1: + { + if(menu == &encrypt_menu_1) + kdf_delay_menu(); + break; + } + case 2: + { + char temp_pass[sizeof(enc_password)]; + temp_pass[0] = '\0'; + + rb->splash(HZ * 2, "Enter current password:"); + + if(rb->kbd_input(temp_pass, sizeof(temp_pass)) < 0) + break; + + if(rb->strcmp(enc_password, temp_pass)) + { + rb->splash(HZ * 2, "Wrong password!"); + break; + } + + encrypted = 0; + + background_save(); + + rb->splash(HZ, "Success."); + } + break; + case 3: + default: + break; + } +} + +static void adv_menu(void) +{ + MENUITEM_STRINGLIST(menu, "Advanced", NULL, + "Edit Account", + "Export Accounts", + "Encryption", + "Delete ALL Accounts", +#if CONFIG_RTC + "Select Time Zone", +#endif + "Back"); + + bool quit = false; + int sel = 0; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + edit_accts(); + break; + case 1: + export_menu(); + break; + case 2: + { + encrypt_menu(); + break; + } + case 3: + if(danger_confirm()) + { + rb->mutex_lock(&save_mutex); + next_slot = 0; + rb->mutex_unlock(&save_mutex); + save_accts(); + rb->splash(HZ, "It is done, my master."); + } + else + rb->splash(HZ, "Not confirmed."); + break; +#if CONFIG_RTC + case 4: + { + int old_offs = time_offs; + rb->mutex_lock(&save_mutex); + time_offs = get_time_offs(); + rb->mutex_unlock(&save_mutex); + if(time_offs != old_offs) + background_save(); + break; + } + case 5: +#else + case 4: +#endif + quit = 1; + break; + default: + break; + } + } +} + +/* displays the help text */ +static void show_help(void) +{ + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif + +#ifdef HAVE_LCD_BITMAP + rb->lcd_setfont(FONT_UI); +#endif + + static char *help_text[] = { "Password Manager", "", + "", + "Introduction", "", + "This", "plugin", "allows", "you", "to", "generate", "one-time", "passwords", "as", "a", "second", "factor", "of", "authentication", "for", "online", "services", "which", "support", "it,", "such", "as", "GitHub", "and", "Google.", + "This", "plugin", "supports", "both", "counter-based", "(HOTP),", "and", "time-based", "(TOTP)", "password", "schemes.", + "It", "also", "supports", "storing", "static", "passwords", "securely.", + "", + "", + "Time Zone Configuration", "", + "On", "the", "first", "run", "of", "the", "plugin,", "you", "are", "asked", "for", "the", "time", "zone", "to", "which", "your", "system", "clock", "is", "set.", + "If", "you", "need", "to", "change", "this", "setting", "later,", "it", "is", "available", "under", "the", "'Advanced'", "menu", "option.", + "", + "", + "Account Setup", "", + "To", "add", "a", "new", "account,", "choose", "the", "'Import", "Account(s)'", "menu", "option.", + "There", "are", "two", "ways", "to", "import", "an", "account,", "either", "from", "a", "file", "containing", "account", "information", "in", "URI", "format,", "or", "manual", "entry.", + "", + "", + "URI Import", "", + "This", "method", "of", "adding", "an", "account", "reads", "a", "list", "of", "URIs", "from", "a", "file.", + "It", "expects", "each", "URI", "to", "be", "on", "a", "line", "by", "itself", "in", "the", "following", "format:", "", + "", + "otpauth://[hotp", "OR", "totp]/[account", "name]?secret=[Base32", "secret][&counter=X][&period=X][&digits=X]", "", + "", + "An", "example", "is", "shown", "below,", "provisioning", "a", "TOTP", "key", "for", "an", "account", "called", "``bob'':", "", + "", + "otpauth://totp/bob?secret=JBSWY3DPEHPK3PXP", "", + "", + "Any", "other", "URI", "options", "are", "not", "supported", "and", "will", "be", "ignored.", + "", + "Most", "services", "will", "provide", "a", "scannable", "QR", "code", "that", "encodes", "a", "OTP", "URI.", + "In", "order", "to", "use", "those,", "first", "scan", "the", "QR", "code", "separately", "and", "save", "the", "URI", "to", "a", "file", "on", "your", "device.", + "If", "necessary,", "rewrite", "the", "URI", "so", "it", "is", "in", "the", "format", "shown", "above.", + "For", "example,", "GitHub's", "URI", "has", "a", "slash", "after", "the", "provider.", + "In", "order", "for", "this", "URI", "to", "be", "properly", "parsed,", "you", "must", "rewrite", "the", "account", "name", "so", "that", "it", "does", "not", "contain", "a", "slash.", + "", + "", + "Manual Import", "", + "If", "direct", "URI", "import", "is", "not", "possible,", "the", "plugin", "supports", "the", "manual", "entry", "of", "data", "associated", "with", "an", "account.", + "After", "you", "select", "the", "'Manual", "Entry'", "option,", "it", "will", "prompt", "you", "for", "an", "account", "name.", + "You", "may", "type", "anything", "you", "wish,", "but", "it", "should", "be", "memorable.", + "It", "will", "then", "prompt", "you", "for", "the", "Base32-encoded", "secret.", + "Most", "services", "will", "provide", "this", "to", "you", "directly,", "but", "some", "may", "only", "provide", "you", "with", "a", "QR", "code.", + "In", "these", "cases,", "you", "must", "scan", "the", "QR", "code", "separately,", "and", "then", "enter", "the", "string", "following", "the", "'secret='", "parameter", "on", "your", "Rockbox", "device", "manually.", + "", + "On", "devices", "with", "a", "real-time", "clock,", "the", "plugin", "will", "ask", "whether", "the", "account", "is", "a", "time-based", "account", "(TOTP).", + "If", "you", "answer", "'yes'", "to", "this", "question,", "it", "will", "ask", "for", "further", "information", "regarding", "the", "account.", + "Usually", "it", "is", "safe", "to", "accept", "the", "defaults", "here.", + "However,", "if", "your", "device", "lacks", "a", "real-time", "clock,", "the", "plugin's", "functionality", "will", "be", "restricted", "to", "HMAC-based", "(HOTP)", "accounts", "only.", + "If", "this", "is", "the", "case,", "the", "plugin", "will", "prompt", "you", "for", "information", "regarding", "the", "HOTP", "setup.", + "Again,", "it", "is", "usually", "safe", "to", "accept", "the", "defaults.", + "", + "", + "Account Export", "", + "This", "plugin", "allows", "you", "to", "export", "account", "data", "to", "a", "file", "for", "backup", "and", "transfer", "purposes.", + "This", "option", "is", "located", "under", "the", "'Advanced'", "menu.", + "It", "will", "prompt", "for", "for", "a", "filename,", "and", "will", "write", "all", "your", "account", "data", "to", "the", "specified", "file.", + "This", "file", "can", "be", "imported", "by", "this", "plugin", "using", "the", "'From", "URI", "List'", "option", "when", "importing.", + "Please", "note", "that", "you", "should", "not", "attempt", "to", "copy", "the", "'passmgr.dat'", "from", "the", ".rockbox", "directory", "to", "another", "device.", + "", + "", + "Encryption", "", + "This", "plugin", "supports", "the", "optional", "encryption", "of", "account", "data", "while", "stored", "on", "disk.", + "This", "feature", "is", "located", "under", "the", "'Advanced'", "menu", "option.", + "Upon", "enabling", "this", "feature,", "you", "must", "enter", "an", "encryption", "password", "that", "will", "need", "to", "be", "entered", "each", "time", "the", "plugin", "starts", "up.", + "It", "is", "recommended", "that", "you", "use", "a", "strong,", "alphanumeric", "password", "of", "at", "least", "8", "characters", "in", "order", "to", "frustrate", "attempts", "to", "guess", "the", "password.", + "Be", "sure", "not", "to", "forget", "this", "password.", + "In", "the", "event", "that", "the", "password", "is", "lost,", "it", "is", "nearly", "impossible", "to", "recover", "your", "account", "data.", + "", + "", + "Implementation Details", "", + "Account", "data", "is", "encrypted", "with", "128-bit", "AES", "encryption", "in", "counter", "mode.", + "The", "key", "is", "derived", "from", "the", "your", "password", "and", "a", "nonce", "by", "using", "PBKDF2-HMAC-SHA1,", "with", "a", "variable", "number", "of", "iterations,", "calibrated", "by", "default", "to", "take", "250", "milliseconds.", + "This", "parameter", "can", "be", "adjusted", "using", "the", "'Change", "KDF", "Delay'", "option", "under", "the", "'Encryption'", "submenu.", + "The", "nonce", "is", "generated", "from", "the", "system's", "current", "tick", "and", "the", "real-time", "clock,", "if", "available,", "making", "collision", "unlikely.", + "Some", "later-model", "iPods", "have", "a", "hardware", "AES", "core", "with", "a", "hardcoded,", "device-specific", "key", "that", "cannot", "easily", "be", "extracted.", + "When", "available,", "the", "device-specific", "key", "is", "used", "to", "encrypt", "the", "actual", "encryption", "key,", "tying", "the", "ciphertext", "to", "the", "device,", "making", "a", "brute-force", "attack", "more", "difficult.", + "One", "should", "note", "that", "this", "does", "not", "rely", "completely", "rely", "on", "the", "hardware", "encryption", "key,", "it", "merely", "utilizes", "it", "as", "part", "of", "defense", "in", "depth.", + "", + "", + "Troubleshooting", "", + "If", "time-based", "passwords", "and", "not", "working", "properly,", "ensure", "that", "your", "system", "clock", "is", "accurate", "to", "within", "30", "seconds", "of", "the", "authenticating", "server's", "clock,", "and", "that", "the", "proper", "time", "zone", "is", "configured", "within", "the", "plugin.", + "Be", "sure", "to", "account", "for", "Daylight", "Savings", "Time,", "if", "applicable.", + "", + "", + "Supported Features", "", +#if !CONFIG_RTC + "This", "device", "lacks", "a", "real-time", "clock,", "and", "thus", "time-based", "(TOTP)", "passwords", "are", "not", "supported.", + "", +#endif +#if CONFIG_CPU == S5L8702 && !defined(SIMULATOR) + "This", "device", "has", "a", "hardware", "AES", "core", "that", "will", "be", "used", "to", "further", "protect", "your", "data", "by", "tying", "it", "to", "this", "device.", + "", +#else + "This", "device", "does", "not", "have", "a", "hardware", "AES", "core.", + "The", "security", "of", "the", "encryption", "thus", "relies", "solely", "on", "your", "password.", + "", +#endif +#ifdef USB_ENABLE_HID + "This", "device", "has", "the", "ability", "to", "type", "passwords", "directly", "to", "a", "host", "computer", "over", "the", "USB", "connection.", + "", +#endif + }; + struct style_text style[] = { + { 0, TEXT_CENTER | TEXT_UNDERLINE }, + { 3, C_RED }, + { 50, C_RED }, + { 91, C_RED }, + { 127, C_RED }, + { 280, C_RED }, + { 468, C_RED }, + { 548, C_RED }, + { 644, C_RED }, + { 787, C_RED }, + { 835, C_RED }, + LAST_STYLE_ITEM + }; + + display_text(ARRAYLEN(help_text), help_text, style, NULL, true); +} + +#ifdef USB_ENABLE_HID + +#define FORCE_EXEC_THRES (HZ/3) +#define TYPE_DELAY (HZ / 25) + +static bool wait_for_usb(void) +{ + if(!rb->usb_inserted()) + { + /* wait for a USB connection */ + + rb->splash(0, "Waiting for USB, hold any button to abort..."); + + int oldbutton = 0; + int ticks_held = 0; + long last_tick = 0; + while(1) + { + int button = rb->button_get(true); + if(button == SYS_USB_CONNECTED) + { + break; + } + else if(button) + { + /* check if a key is being held down */ + + if(oldbutton == 0) + { + oldbutton = button; + + ticks_held = 0; + last_tick = *rb->current_tick; + } + else if(button == oldbutton || button == (oldbutton | BUTTON_REPEAT)) + { + int dt = *rb->current_tick - last_tick; + if(dt) + { + ticks_held += dt; + last_tick = *rb->current_tick; + if(ticks_held >= FORCE_EXEC_THRES) + return false; + } + } + } + } + + /* wait a bit to let the host recognize us... */ + rb->sleep(HZ / 2); + } + return true; +} + +static void send(int status) +{ + rb->usb_hid_send(HID_USAGE_PAGE_KEYBOARD_KEYPAD, status); +} + +/* Rockbox's HID driver supports up to 4 keys simultaneously, 1 in each byte */ + +static void add_key(int *keystate, unsigned *nkeys, int newkey) +{ + *keystate = (*keystate << 8) | newkey; + if(nkeys) + (*nkeys)++; +} + +struct char_mapping { + char c; + int key; +}; + +static struct char_mapping shift_tab[] = { + { '~', HID_KEYBOARD_BACKTICK }, + { '!', HID_KEYBOARD_1 }, + { '@', HID_KEYBOARD_2 }, + { '#', HID_KEYBOARD_3 }, + { '$', HID_KEYBOARD_4 }, + { '%', HID_KEYBOARD_5 }, + { '^', HID_KEYBOARD_6 }, + { '&', HID_KEYBOARD_7 }, + { '*', HID_KEYBOARD_8 }, + { '(', HID_KEYBOARD_9 }, + { ')', HID_KEYBOARD_0 }, + { '_', HID_KEYBOARD_HYPHEN }, + { '+', HID_KEYBOARD_EQUAL_SIGN }, + { '}', HID_KEYBOARD_RIGHT_BRACKET }, + { '{', HID_KEYBOARD_LEFT_BRACKET }, + { '|', HID_KEYBOARD_BACKSLASH }, + { '"', HID_KEYBOARD_QUOTE }, + { ':', HID_KEYBOARD_SEMICOLON }, + { '?', HID_KEYBOARD_SLASH }, + { '>', HID_KEYBOARD_DOT }, + { '<', HID_KEYBOARD_COMMA }, +}; + +static struct char_mapping char_tab[] = { + { ' ', HID_KEYBOARD_SPACEBAR }, + { '`', HID_KEYBOARD_BACKTICK }, + { '-', HID_KEYBOARD_HYPHEN }, + { '=', HID_KEYBOARD_EQUAL_SIGN }, + { '[', HID_KEYBOARD_LEFT_BRACKET }, + { ']', HID_KEYBOARD_RIGHT_BRACKET }, + { '\\', HID_KEYBOARD_BACKSLASH }, + { '\'', HID_KEYBOARD_QUOTE }, + { ';', HID_KEYBOARD_SEMICOLON }, + { '/', HID_KEYBOARD_SLASH }, + { ',', HID_KEYBOARD_COMMA }, + { '.', HID_KEYBOARD_DOT }, + { '\t',HID_KEYBOARD_TAB }, +}; + +static void add_char(int *keystate, unsigned *nkeys, char c) +{ + (void) keystate; (void) nkeys; (void) c; + if('a' <= c && c <= 'z') + { + add_key(keystate, nkeys, c - 'a' + HID_KEYBOARD_A); + } + else if('A' <= c && c <= 'Z') + { + add_key(keystate, nkeys, HID_KEYBOARD_LEFT_SHIFT); + add_key(keystate, nkeys, c - 'A' + HID_KEYBOARD_A); + } + else if('0' <= c && c <= '9') + { + if(c == '0') + add_key(keystate, nkeys, HID_KEYPAD_0_AND_INSERT); + else + add_key(keystate, nkeys, c - '1' + HID_KEYPAD_1_AND_END); + } + else + { + /* search the character table */ + for(unsigned int i = 0; i < ARRAYLEN(char_tab); ++i) + { + if(char_tab[i].c == c) + { + add_key(keystate, nkeys, char_tab[i].key); + return; + } + } + + /* search the shift-mapping table */ + for(unsigned int i = 0; i < ARRAYLEN(shift_tab); ++i) + { + if(shift_tab[i].c == c) + { + add_key(keystate, nkeys, HID_KEYBOARD_LEFT_SHIFT); + add_key(keystate, nkeys, shift_tab[i].key); + return; + } + } + + rb->splashf(HZ, "WARNING: could not type character '%c'!", c); + } +} + +static void send_string(const char *str) +{ + while(*str) + { + int string_state = 0; + if(!*str) + break; + add_char(&string_state, NULL, *str); + + send(string_state); + + ++str; + + rb->sleep(TYPE_DELAY); + } +} + +static bool enable_numlock(void) +{ + /* check numlock status */ + bool change_numlock = !(rb->usb_hid_leds() & 0x1); + if(change_numlock) + rb->usb_hid_send(HID_USAGE_PAGE_KEYBOARD_KEYPAD, HID_KEYPAD_NUM_LOCK_AND_CLEAR); + return change_numlock; +} + +static void type_code(int acct) +{ + if(!wait_for_usb()) + return; + + rb->splash(0, "Typing..."); + + bool change_numlock = enable_numlock(); + + switch(accounts[acct].type) + { + case TYPE_HOTP: + case TYPE_TOTP: + { + int code = next_code(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(buf, sizeof(buf), fmt_buf, code); + + char *ptr = buf; + + while(*ptr) + { + char c = *ptr++; + if(c == '0') + rb->usb_hid_send(HID_USAGE_PAGE_KEYBOARD_KEYPAD, HID_KEYPAD_0_AND_INSERT); + else + rb->usb_hid_send(HID_USAGE_PAGE_KEYBOARD_KEYPAD, c - '1' + HID_KEYPAD_1_AND_END); + rb->sleep(TYPE_DELAY); + } + if(accounts[acct].type == TYPE_HOTP) + background_save(); + break; + } + case TYPE_STATIC: + send_string(accounts[acct].secret); + break; + default: + break; + } + + rb->usb_hid_send(HID_USAGE_PAGE_KEYBOARD_KEYPAD, HID_KEYBOARD_RETURN); + + if(change_numlock) + rb->usb_hid_send(HID_USAGE_PAGE_KEYBOARD_KEYPAD, HID_KEYPAD_NUM_LOCK_AND_CLEAR); + + rb->splash(0, "Done."); + + /* wait a while to prevent accidental code generation */ + rb->sleep(HZ / 2); + while(1) + { + int button = rb->button_get(true); + if(button && !(button & BUTTON_REL)) + break; + rb->yield(); + } + + rb->lcd_update(); +} + +static void type_codes(void) +{ + if(!rb->global_settings->usb_hid) + { + rb->splashf(HZ * 4, "Please enable USB HID in the system settings."); + } + acct_menu("Type Password", type_code); +} +#endif + +/* based on keybox */ + +static const char* list_cb(int selected_item, void *data, + char *buffer, size_t buffer_len) +{ + (void) data; + rb->snprintf(buffer, buffer_len, "%s", accounts[selected_item].name); + return buffer; +} + +static void acct_menu(char *title, void (*cb)(int acct)) +{ + struct gui_synclist list; + + rb->gui_synclist_init(&list, &list_cb, NULL, false, 1, NULL); + rb->gui_synclist_set_title(&list, title, NOICON); + rb->gui_synclist_set_icon_callback(&list, NULL); + rb->gui_synclist_set_nb_items(&list, next_slot); + rb->gui_synclist_limit_scroll(&list, false); + rb->gui_synclist_select_item(&list, 0); + + bool done = false; + + while (!done) + { + rb->gui_synclist_draw(&list); + int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); +#ifdef USB_ENABLE_HID + if(cb != type_code || button != SYS_USB_CONNECTED) +#endif + if (rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON)) + continue; + + switch (button) + { + case ACTION_STD_OK: + cb(rb->gui_synclist_get_sel_pos(&list)); + rb->gui_synclist_set_nb_items(&list, next_slot); + if(rb->gui_synclist_get_sel_pos(&list) >= next_slot) + rb->gui_synclist_select_item(&list, next_slot - 1); + break; + case ACTION_STD_CONTEXT: + if(cb != edit_menu) + edit_menu(rb->gui_synclist_get_sel_pos(&list)); + rb->gui_synclist_set_nb_items(&list, next_slot); + if(rb->gui_synclist_get_sel_pos(&list) >= next_slot) + rb->gui_synclist_select_item(&list, next_slot - 1); + break; + case ACTION_STD_CANCEL: + done = true; + break; + } + rb->yield(); + } + + return; + + rb->lcd_clear_display(); + /* native menus don't seem to support dynamic names easily, so we + * roll our own */ + static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; + int idx = 0; + if(next_slot > 0) + { + rb->lcd_puts(0, 0, title); + rb->lcd_putsf(0, 1, "%s", accounts[0].name); + rb->lcd_update(); + } + else + { + rb->splash(HZ * 2, "No accounts configured!"); + return; + } + while(1) + { + int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); + switch(button) + { + case PLA_LEFT: + --idx; + if(idx < 0) + idx = next_slot - 1; + break; + case PLA_RIGHT: + ++idx; + if(idx >= next_slot) + idx = 0; + break; + case PLA_SELECT: + cb(idx); + if(idx >= next_slot) + idx = 0; + if(next_slot == 0) + return; + break; + case PLA_UP: + case PLA_CANCEL: + case PLA_EXIT: + return; + default: +#ifdef USB_ENABLE_HID + if(cb != type_code) +#endif + exit_on_usb(button); + break; + } + rb->lcd_clear_display(); + rb->lcd_puts(0, 0, title); + rb->lcd_putsf(0, 1, "%s", accounts[idx].name); + rb->lcd_update(); + rb->yield(); + } +} + +static bool self_check(void) +{ + /* RFC 4226 */ + if(HOTP("12345678901234567890", rb->strlen("12345678901234567890"), 1, 6) != 287082) + return false; + + /* do a 2-byte KDF just to check that I didn't break TOO many things :P */ + + unsigned char out[2]; + char tmp[4 + 4]; + + PBKDF2("password", 8, "salt", 4, 2, out, 2, tmp); + + if(out[0] != 0xea || out[1] != 0x6c) + return false; + + return true; +} + +/* this is the plugin entry point */ +enum plugin_status plugin_start(const void* parameter) +{ + (void)parameter; + + if(!self_check()) + { + rb->splash(HZ * 4, "Self-test failed! REPORT ME!"); + return PLUGIN_ERROR; + } + + size_t bufsz; + accounts = rb->plugin_get_buffer(&bufsz); + max_accts = bufsz / sizeof(struct account_t); + + atexit(erase_sensitive_info); + + if(!read_accts()) + { +#if CONFIG_RTC + /* first-run config */ + time_offs = get_time_offs(); +#endif + kdf_iters = calc_kdf_iters(KDF_DEFAULT); + } + + /* initialize background saving thread */ + rb->mutex_init(&save_mutex); + background_id = rb->create_thread(background_thread, background_stack, + sizeof(background_stack), 0, + "background_save" IF_PRIO(, PRIORITY_BACKGROUND) IF_COP(, COP)); + + MENUITEM_STRINGLIST(menu, "Password Manager", NULL, + "Show Password", // 0 +#ifdef USB_ENABLE_HID + "Type Password", // 1 +#endif + "Import Account(s)", // 1,2 + "Help", // 2,3 + "Advanced", // 3,4 + "Quit"); // 4,5 + + bool quit = false; + int sel = 0; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + gen_codes(); + break; +#ifdef USB_ENABLE_HID + case 1: + type_codes(); + break; + case 2: + add_acct(); + break; + case 3: + show_help(); + break; + case 4: + adv_menu(); + break; + case 5: + quit = 1; + break; +#else + case 1: + add_acct(); + break; + case 2: + show_help(); + break; + case 3: + adv_menu(); + break; + case 4: + quit = 1; + break; +#endif + default: + break; + } + } + + rb->mutex_lock(&save_mutex); // make sure we aren't saving + + /* kill the background thread */ + kill_background = true; + if(background_id >= 0) + rb->thread_wait(background_id); + + /* save to disk */ + save_accts(); + + /* tell Rockbox that we have completed successfully */ + return PLUGIN_OK; +} diff --git a/apps/plugins/passmgr/passmgr.make b/apps/plugins/passmgr/passmgr.make new file mode 100644 index 0000000..341eb64 --- /dev/null +++ b/apps/plugins/passmgr/passmgr.make @@ -0,0 +1,27 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +PASSMGRSRCDIR := $(APPSDIR)/plugins/passmgr +PASSMGRBUILDDIR := $(BUILDDIR)/apps/plugins/passmgr + +ROCKS += $(PASSMGRBUILDDIR)/passmgr.rock + +PASSMGR_SRC := $(call preprocess, $(PASSMGRSRCDIR)/SOURCES) +PASSMGR_OBJ := $(call c2obj, $(PASSMGR_SRC)) + +# add source files to OTHER_SRC to get automatic dependencies +OTHER_SRC += $(PASSMGR_SRC) + +PASSMGRFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O3 + +$(PASSMGRBUILDDIR)/passmgr.rock: $(PASSMGR_OBJ) + +$(PASSMGRBUILDDIR)/%.o: $(PASSMGRSRCDIR)/%.c $(PASSMGRSRCDIR)/passmgr.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(PASSMGRFLAGS) -c $< -o $@ diff --git a/apps/plugins/passmgr/wordlist.c b/apps/plugins/passmgr/wordlist.c new file mode 100644 index 0000000..c87ec10 --- /dev/null +++ b/apps/plugins/passmgr/wordlist.c @@ -0,0 +1,3 @@ +const char *noun_list[] = { "abacus", "abase", "abbess", "abbey", "abbot", "abdicate", "abdomen", "abdominal", "abduction", "abed", "abet", "abeyance", "abhorrent", "abidance", "abject", "abjure", "able", "ablution", "abnegate", "abnormal", "abominate", "above", "abrade", "abrasion", "abridge", "abrogate", "abrupt", "abscess", "abscissa", "abscond", "absence", "absent", "absolute", "absolve", "absorb", "absorbed", "abstain", "abstruse", "absurd", "abundant", "abused", "abusive", "abut", "abyss", "academic", "academy", "accede", "acceded", "accedes", "accented", "accept", "accepted", "access", "accession", "accessory", "accident", "acclaim", "acclimate", "accompany", "accorded", "accordion", "accost", "accosts", "account", "accouter", "accredit", "accuracy", "accurate", "accursed", "accuse", "accusing", "accustom", "acerbity", "acetate", "acetic", "ache", "acid", "acidify", "ackee", "acme", "acoustic", "acquaint", "acquiesce", "acquire", "acquit", "acquittal", "acreage", "acreages", "acrid", "acrimony", "acrobat", "activate", "activist", "actuality", "actuary", "actuate", "acumen", "acute", "acutely", "adamant", "adaptable", "adapters", "added", "addendum", "addle", "addled", "address", "adduce", "adelgid", "adenoma", "adequate", "adhere", "adherence", "adherent", "adheres", "adhesion", "adieu", "adios", "adjacency", "adjacent", "adjudge", "adjunct", "adjutant", "admirals", "admonish", "ado", "adoration", "adorning", "adroit", "adulthood", "adumbrate", "advance", "advanced", "advancing", "advent", "adverse", "adversely", "adversity", "advert", "advisable", "adviser", "advisory", "advocacy", "advocate", "aerial", "aerobics", "aeronaut", "aerostat", "affable", "affect", "affected", "affiliate", "affinity", "affix", "afflict", "affluence", "affluent", "affront", "affronts", "afire", "afoot", "aforesaid", "afresh", "aft", "after", "afterward", "age", "aggravate", "aggregate", "aggress", "aggrieve", "aghast", "agile", "agility", "agitate", "agitated", "agitprop", "agnostic", "agog", "agonal", "agonized", "agrarian", "ague", "aid", "ailment", "aim", "airbag", "airfare", "airfield", "airspace", "airwaves", "airy", "akin", "alabaster", "alacrity", "albeit", "albino", "album", "alchemy", "alcohol", "alcove", "alder", "alderman", "alert", "algae", "algebra", "alias", "alien", "alienable", "alienate", "alike", "aliment", "alkali", "alkaline", "allay", "allege", "alleged", "allegory", "alleviate", "alley", "alleyway", "alliance", "allies", "allocate", "allot", "allotment", "allowed", "allude", "allusion", "allusive", "alluvion", "ally", "almanac", "almost", "alone", "aloof", "alpha", "alphabet", "altar", "alter", "altercate", "alternate", "although", "altitude", "alto", "altruism", "altruist", "amalgam", "amass", "amateur", "amatory", "amazed", "amber", "ambiguous", "ambitious", "ambrosia", "ambrosial", "ambulance", "ambulate", "ambush", "amenable", "amended", "amicable", "amish", "amity", "amongst", "amorous", "amorphous", "amount", "amour", "ampere", "ampersand", "amplified", "amplitude", "amply", "amputate", "amusement", "anagram", "analog", "analogous", "analogy", "analysed", "analyst", "analyze", "anarchy", "anathema", "anatomy", "ancestry", "anchor", "anchored", "ancient", "ancillary", "anecdote", "anemia", "anemic", "anew", "angel", "angelic", "angler", "angstrom", "anguish", "angular", "anhydrous", "animal", "animate", "anime", "animosity", "anklet", "annalist", "annals", "annex", "annexed", "annotate", "annual", "annuity", "anode", "anonymous", "another", "answer", "answering", "ante", "antecede", "antedate", "antenatal", "anterior", "anteroom", "anthem", "anthology", "antic", "antidote", "antilogy", "antiphon", "antiphony", "antipodes", "antiquary", "antiquate", "antique", "antitoxin", "antonym", "anxious", "anymore", "anyplace", "anything", "apart", "apathy", "aperitif", "aperture", "apex", "aphorism", "apiary", "apogee", "apology", "apostasy", "apostate", "apostle", "appall", "apparatus", "apparel", "apparent", "appeal", "appealed", "appease", "appellate", "append", "appertain", "appoint", "apposite", "appraise", "apprehend", "apprise", "approved", "aquarium", "aqueduct", "aqueous", "arachnid", "arb", "arbiter", "arbitrary", "arbitrate", "arbor", "arboreal", "arboretum", "arbs", "arcade", "arch", "archaic", "archaism", "archangel", "arched", "archetype", "archival", "archive", "archrival", "ardent", "ardor", "arid", "arise", "armada", "armament", "armature", "armful", "armistice", "armless", "armor", "armorers", "armory", "armrest", "arnica", "aroma", "around", "aroused", "arraign", "arrange", "arrant", "array", "arrayed", "arrear", "arrested", "arrival", "arrogant", "arrogate", "arrowing", "arsenate", "artery", "artful", "artifice", "artistry", "artless", "aryan", "ascendant", "ascension", "ascent", "ascetic", "ascribe", "asexual", "ashamed", "ashen", "ashore", "asiatic", "askance", "asking", "asperity", "aspirant", "aspire", "assailant", "assassin", "assault", "assay", "assent", "assertive", "assess", "assessor", "assets", "assiduous", "assignee", "associate", "assonance", "assonant", "assonate", "assuage", "assume", "assuredly", "asterisk", "asters", "asthma", "astronaut", "astute", "asylums", "atheism", "athirst", "athwart", "atomize", "atomizer", "atonal", "atone", "atonement", "atrocious", "atrocity", "attache", "attack", "attacked", "attain", "attend", "attest", "auburn", "auction", "auctions", "audacious", "audible", "audition", "auditory", "audits", "augment", "augur", "aura", "aural", "auricle", "auricular", "aurora", "auspice", "austere", "autarchy", "auteur", "authentic", "authority", "autocracy", "autocrat", "automaton", "autonomy", "autopsy", "autumn", "autumnal", "auxiliary", "avalanche", "avarice", "aver", "averse", "aversion", "avert", "aviaries", "aviary", "aviator", "avidity", "avidly", "avocation", "avow", "await", "awaits", "awaken", "awakened", "awards", "awash", "awesome", "awful", "awry", "aww", "axes", "axioms", "aye", "azalea", "azure", "babble", "babe", "baby", "babysit", "back", "backdrop", "backed", "backstop", "bacterium", "badger", "badgered", "baffle", "baffled", "bag", "bagel", "baggage", "baggier", "bagpipe", "bailiff", "bairn", "bait", "baize", "baked", "bakeware", "balancing", "baldly", "bale", "baleful", "ballad", "ballpark", "balm", "balsa", "balsam", "bamboo", "banal", "band", "banjo", "banned", "banquet", "banter", "baptisms", "baptize", "barbarian", "barbaric", "barbells", "barbeque", "barcarole", "barefoot", "bargain", "baritone", "barium", "bark", "barley", "barograph", "barometer", "barrages", "barrel", "barreled", "barrier", "barring", "barstool", "bask", "basket", "bass", "basso", "baste", "bastion", "batch", "bath", "bather", "baton", "batons", "battalion", "batten", "batter", "batting", "bauble", "bawl", "bawled", "bayonet", "beacon", "bead", "beaded", "beads", "bear", "bearer", "beatify", "beatitude", "beatnik", "beau", "becalm", "beck", "bedaub", "bedding", "bedeck", "bedlam", "bedside", "beep", "beeper", "befog", "before", "befriend", "befuddle", "beget", "beginner", "begrudge", "beguine", "behavior", "beheaded", "behemoth", "behest", "beholden", "belate", "belated", "belay", "belie", "believe", "belittle", "belle", "bellicose", "bellmen", "bellow", "belly", "beloved", "below", "bemoan", "bemoaned", "bench", "benched", "bended", "benefice", "benefit", "bengal", "benign", "benignant", "benignity", "benison", "bequeath", "bereave", "bereaved", "berth", "beseech", "beset", "besides", "besmear", "besmirch", "bespeak", "best", "bestial", "bestow", "bestrew", "bestride", "bethink", "betide", "betimes", "betrayed", "betroth", "betrothal", "better", "between", "betwixt", "bevel", "beveled", "bewilder", "bias", "bib", "bibulous", "bidding", "bide", "biennial", "bier", "bigamist", "bigamy", "biggie", "bight", "bigotry", "bike", "bilateral", "bile", "bilingual", "billfish", "billow", "billows", "billowy", "binding", "bingeing", "bio", "biograph", "biography", "biology", "biomes", "biped", "biryani", "bitter", "biz", "bizarre", "blab", "blabbing", "blackbird", "blacked", "blacklist", "blackness", "blank", "blanket", "blase", "blaspheme", "blatant", "blaze", "blazon", "bleak", "blemish", "blended", "blight", "blind", "blinded", "blink", "blipped", "blithe", "blitz", "blizzard", "blockade", "blocker", "blocking", "blond", "blonde", "bloodbath", "bloodied", "bloodily", "bloodline", "bloom", "bloomed", "blouses", "blousy", "blowhole", "blowtorch", "blue", "bluebird", "bluish", "blunder", "blunt", "blurb", "blurring", "blush", "bluster", "boards", "boatload", "boatswain", "boatyard", "bobble", "boded", "bodice", "bodily", "bog", "bogus", "bold", "bole", "bolero", "boll", "bolster", "bolts", "bomb", "bombard", "bombast", "bombings", "bomblets", "bombs", "bond", "bondsman", "boob", "boobs", "bookcase", "bookend", "bookmark", "bookworm", "boorish", "boost", "booster", "boot", "bootleg", "bordello", "bordering", "bore", "borers", "boring", "borough", "bosom", "boss", "botanical", "botanist", "botanize", "botany", "bothered", "bottles", "bottling", "bouffant", "bounce", "bouncing", "boundary", "bountiful", "bow", "bowel", "bowing", "bowl", "bowler", "boxing", "boxlike", "boxwood", "boycott", "brace", "brackets", "brae", "braggart", "brains", "brainwash", "branch", "brandish", "brassy", "bravado", "bravo", "brawl", "brawls", "brawny", "bray", "braze", "brazenly", "brazier", "breach", "breadth", "breakaway", "breakdown", "breaker", "breakfast", "breath", "breech", "breed", "brethren", "brevity", "bribery", "bridge", "bridle", "brief", "briefers", "briers", "brigade", "brigadier", "brigand", "brighten", "brightly", "brim", "brimstone", "brine", "bristle", "brittle", "broach", "broaches", "broadcast", "broader", "brochure", "brogan", "brogans", "brogue", "broil", "broiler", "brokerage", "brome", "bromine", "bronchus", "bronzers", "brooch", "brook", "broom", "broth", "brow", "browbeat", "brown", "brownie", "brownish", "bruised", "brushing", "brushy", "brusque", "bubble", "bubonic", "buck", "bucket", "buckled", "buff", "buffet", "buffeted", "buffoon", "bugging", "bugle", "build", "bulb", "bulbous", "bulging", "bulk", "bulldoze", "bulled", "bullets", "bullock", "bully", "bulrush", "bulwark", "bum", "bumper", "bumpers", "bumptious", "bumpy", "bunch", "bundle", "bunghole", "bungle", "bungled", "buoyancy", "buoyant", "burden", "bureau", "burger", "burgess", "burgher", "burgundy", "burlap", "burned", "burnet", "burnish", "burp", "burping", "burrows", "bursar", "busman", "bussed", "busses", "bust", "bustards", "busters", "bustier", "busting", "bustle", "busty", "busybody", "butcher", "butt", "butte", "buttered", "buttress", "buy", "buyouts", "buzzes", "byline", "cabal", "cabalism", "cabin", "cabinet", "cabriole", "cacao", "cacophony", "cactus", "caddies", "caddis", "caddy", "cadence", "cadenza", "cadet", "caitiff", "cajole", "cajolery", "calculus", "calf", "calibers", "calipers", "call", "called", "caller", "callosity", "callow", "calorie", "calumny", "came", "cameo", "camera", "camp", "campaign", "canadian", "canard", "canary", "candid", "candor", "canine", "canines", "canister", "cannon", "canny", "canon", "canonize", "canopy", "cant", "cantata", "canteen", "canto", "canvases", "canvass", "capable", "capably", "capacious", "capillary", "capital", "capo", "capon", "caprice", "capsizes", "caption", "captious", "captivate", "captor", "capture", "carbonate", "carbos", "carcass", "card", "cardiac", "cardinal", "care", "caret", "cargo", "cargoes", "caring", "carload", "carnage", "carnal", "carnival", "carotene", "carouse", "carped", "carpentry", "carpet", "carpeted", "carried", "carrion", "carrot", "cartilage", "cartload", "cartoons", "cartridge", "carve", "case", "cased", "caseload", "casement", "cases", "cashier", "cashmere", "casings", "cast", "caste", "castigate", "casting", "casual", "casualty", "cat", "cataclysm", "catalans", "catalyst", "catalyze", "cataract", "cathode", "catlike", "caucus", "caudal", "causal", "causally", "cause", "caused", "caustic", "cauterize", "caution", "caviar", "ceaseless", "cede", "celebrate", "celeriac", "cell", "cellar", "cenotaph", "censor", "census", "censuses", "cent", "centenary", "centrally", "centurion", "ceramic", "cereal", "cessation", "cession", "chafe", "chaff", "chagrin", "chain", "chair", "chalk", "chameleon", "champagne", "chancery", "channel", "chanting", "chaos", "chap", "chapel", "char", "charge", "charlatan", "charmed", "charming", "charred", "chasm", "chasten", "chastise", "chastity", "chasuble", "chateau", "chattel", "cheating", "check", "checkered", "checkup", "cheer", "cheerio", "cheesy", "cherished", "chervil", "chicano", "chick", "chief", "chieftain", "chiffon", "chill", "chilled", "chinaman", "ching", "chinooks", "chip", "chipmunk", "chipped", "chipping", "chirpy", "chisel", "chiseled", "chit", "chivalry", "chives", "choice", "choke", "cholera", "choleric", "choral", "christen", "chromatic", "chrome", "chromed", "chub", "chucking", "chuckle", "chug", "chump", "chunk", "chunky", "churlish", "churn", "cichlids", "cipher", "circular", "circulate", "citadel", "cite", "citizen", "civilize", "claim", "claimant", "claimed", "clambers", "clamorous", "clan", "clang", "clangor", "clank", "clap", "clarify", "clarion", "clasped", "clasping", "classic", "classify", "classroom", "classy", "clavicle", "claw", "clay", "clean", "cleansed", "clear", "clearance", "cleared", "clearing", "clearly", "cleaving", "clemency", "clement", "clergyman", "clique", "cloak", "clocks", "clockwork", "clogged", "cloister", "clomps", "cloned", "closer", "closeted", "closure", "clot", "clothe", "clothed", "clothier", "clouds", "cloudy", "clout", "clove", "cloying", "cluck", "clucks", "clue", "clumsier", "clumsy", "coagulant", "coagulate", "coalition", "coarsely", "coasted", "coat", "coated", "coax", "cob", "cobalt", "cobweb", "cocci", "cockpit", "coddle", "code", "codger", "codicil", "codify", "codon", "codpiece", "coerce", "coercion", "coercive", "coexist", "coffin", "cogent", "cognate", "cognizant", "cohere", "coherent", "cohesion", "cohesive", "cohost", "coifed", "coincide", "coined", "coital", "cold", "coldness", "coleus", "coliseum", "collapse", "collate", "colleague", "collector", "collegian", "collide", "collie", "collier", "collision", "colloquy", "collusion", "colon", "colossal", "colossus", "colt", "coltish", "columns", "comatose", "combat", "combated", "combed", "combers", "combine", "combos", "come", "comedic", "comedown", "comely", "comes", "comical", "commando", "commies", "commingle", "committal", "commodity", "commotion", "communist", "commute", "commuting", "compact", "company", "competent", "complain", "complex", "complexly", "compliant", "component", "comport", "compose", "composed", "composer", "compost", "composts", "composure", "compound", "compress", "comprise", "compute", "comrades", "con", "conative", "conceal", "concealed", "concede", "conceit", "conceive", "concerted", "concerto", "concierge", "concise", "concord", "concur", "condense", "conduce", "conducive", "conduit", "confer", "conferee", "confessor", "confetti", "confidant", "confide", "confident", "confine", "confluent", "conform", "confound", "confront", "confuse", "congeal", "congenial", "congest", "conjoin", "conjugal", "conjugate", "conjured", "connect", "connected", "connector", "connive", "connote", "connubial", "conquer", "conscious", "conscript", "consensus", "consent", "consign", "consignee", "consignor", "console", "consoled", "consonant", "consort", "conspire", "constable", "constancy", "constrict", "consul", "consulate", "contagion", "contains", "contender", "contessa", "contest", "continual", "contort", "contrail", "contrary", "contrite", "contrive", "control", "contumacy", "contuse", "contusion", "convene", "converge", "convert", "convex", "convivial", "convolute", "convolve", "convoy", "convulse", "cookbook", "cooked", "cookie", "cookout", "cool", "cooled", "cooperate", "coos", "coped", "copies", "copious", "copiously", "copout", "copped", "copper", "coquette", "cordon", "cork", "corkscrew", "corm", "corn", "cornered", "cornice", "cornmeal", "corollary", "coronet", "corporal", "corporate", "corporeal", "corps", "corpse", "corpulent", "corpuscle", "correctly", "correlate", "corrode", "corroded", "corrosion", "corrosive", "corrupted", "corset", "corseted", "cortical", "cosmetic", "cosmetics", "cosmic", "cosmogony", "cosmology", "cosmonaut", "cosmos", "countess", "couplers", "courage", "course", "courser", "courtesy", "cousin", "covenant", "cover", "covered", "covering", "covert", "covey", "cowardly", "cower", "cowled", "coxswain", "coy", "cozying", "crabs", "crack", "cracked", "cracker", "crackers", "crackpot", "craft", "crag", "cramp", "cramped", "crams", "cranberry", "cranium", "craps", "crapshoot", "crass", "crate", "craving", "craw", "crawly", "crazy", "creak", "cream", "creamery", "creaming", "creamy", "creator", "creche", "credence", "credible", "credibly", "credits", "credulous", "creed", "creep", "creepy", "crematory", "crescent", "cretan", "cretin", "crevasse", "crevice", "cribbage", "cried", "criers", "crimp", "crinkly", "crippled", "crippling", "crisped", "criteria", "criterion", "critical", "critique", "crocheted", "crockery", "crossbar", "crosshair", "crouched", "croup", "crow", "crowbar", "crowd", "crown", "crucible", "crude", "crudites", "cruelty", "cruise", "crumb", "crumpled", "crunch", "crunchy", "crusade", "crushed", "crusted", "crusts", "cry", "crybaby", "cub", "cube", "cubist", "cudgel", "cued", "cueing", "cuff", "culinary", "cull", "culpable", "culprit", "cultivar", "culture", "culvert", "cupful", "cupid", "cupidity", "curable", "curator", "curb", "cured", "curio", "curiously", "curl", "currency", "currently", "cursive", "cursor", "cursory", "curt", "curtail", "curtsy", "curve", "curved", "curving", "custards", "customer", "cutover", "cutting", "cuttings", "cycled", "cycling", "cycloid", "cygnet", "cynic", "cynical", "cynicism", "cynosure", "cyst", "dabbed", "dabs", "dagger", "daily", "dales", "dali", "dalliance", "dam", "dame", "dames", "damned", "damp", "dance", "dancing", "daring", "darkling", "darkly", "darling", "darn", "dashboard", "dashed", "dastard", "date", "dated", "dating", "datum", "dauber", "daunting", "dauntless", "daylight", "daypack", "daytime", "daywear", "dazed", "dazedly", "dazzle", "dazzled", "dba", "deadened", "deadlock", "deafening", "deal", "dearest", "dearth", "debase", "debatable", "debated", "debonair", "debrief", "debut", "decadence", "decagon", "decagram", "decaliter", "decalogue", "decameter", "decamp", "decapod", "decayed", "deceit", "deceitful", "deceive", "decency", "decent", "deception", "deceptive", "decide", "decides", "deciding", "deciduous", "decimal", "decimate", "decipher", "decisive", "decker", "decks", "decorate", "decorous", "decoy", "decoys", "decrease", "decreed", "decrepit", "decrying", "dedicate", "deduce", "deduces", "deed", "deep", "deepen", "deepens", "deer", "deface", "defalcate", "defame", "defamed", "defanged", "default", "defaults", "defective", "defendant", "defensive", "defer", "deference", "defiant", "deficient", "defiled", "definite", "deflate", "deflates", "deflect", "deforest", "deform", "deformed", "deformity", "defraud", "defray", "deft", "degrade", "degraded", "degrading", "dehydrate", "deified", "deify", "deign", "deist", "deists", "deity", "deject", "dejected", "dejection", "delay", "delayed", "delete", "delft", "deli", "delicacy", "delineate", "delirious", "delirium", "delivery", "delude", "deluge", "delusion", "demagogue", "demeanor", "demented", "demerit", "demise", "demo", "demolish", "demon", "demotic", "demulcent", "demur", "demurrage", "dendroid", "denial", "denizen", "denote", "denounce", "dented", "denude", "denuding", "deodorant", "depicted", "deplete", "deplore", "deploy", "deponent", "deport", "deposed", "depositor", "depot", "deprave", "deprecate", "depress", "depth", "deputy", "derby", "derelict", "deride", "derisible", "derision", "derive", "derives", "derrick", "derriere", "descent", "descry", "desert", "deserted", "deserved", "deserving", "desiccant", "designate", "designed", "desirous", "desist", "desktop", "desolate", "despair", "desperado", "desperate", "despite", "despond", "despot", "despotism", "dessert", "destiny", "destitute", "desultory", "detach", "deter", "deterrent", "detest", "detonator", "detract", "detriment", "detrude", "devastate", "develops", "deviance", "deviate", "device", "devices", "devilry", "deviltry", "devious", "devise", "devised", "devolve", "devout", "devoutly", "dexterity", "dhows", "diabolic", "diagnose", "diagnosis", "dial", "dialect", "dialed", "dialogue", "diarist", "diarists", "diarrhea", "diatomic", "diatribe", "dichotomy", "diction", "dictum", "didactic", "diddling", "diffident", "diffuse", "diffusion", "dignitary", "digraph", "digress", "dilate", "dilates", "dilatory", "dildos", "dilemma", "diligence", "dill", "dilute", "dimension", "dimly", "dims", "diner", "dinghies", "dinky", "dint", "diphthong", "diplomacy", "diplomat", "dipping", "direct", "directive", "direst", "dirtied", "disabled", "disagree", "disallow", "disappear", "disarm", "disarmed", "disavow", "disavowal", "disburden", "disburse", "discard", "discern", "disciple", "disclaim", "disco", "discolor", "discomfit", "discord", "discover", "discredit", "discreet", "disdain", "disembody", "disengage", "disfavor", "disfigure", "disgrace", "dish", "dishes", "dishonest", "disinfect", "dislocate", "dislodge", "dismal", "dismissal", "dismount", "disobeys", "disown", "disowned", "disparage", "disparity", "dispel", "dispirit", "displace", "displaced", "disposed", "disposer", "disputed", "disputes", "disquiet", "disregard", "disrepute", "disrobe", "disrupt", "dissect", "dissemble", "dissent", "dissever", "dissipate", "dissolute", "dissolve", "dissonant", "dissuade", "distance", "distant", "distemper", "distend", "distill", "distiller", "distort", "distorted", "distrain", "distrust", "disunion", "diurnal", "diver", "divergent", "diverse", "diversion", "diversity", "divert", "divest", "divide", "divided", "divider", "dividing", "divinely", "divinity", "divisible", "divisive", "divisor", "divulge", "dizzy", "docile", "docket", "dockside", "doctor", "doctorate", "document", "dodo", "doe", "doer", "doff", "dogfight", "doggedly", "doggie", "dogma", "dogmatic", "dogmatize", "dogsled", "dole", "doled", "doleful", "dolesome", "dollies", "dolor", "dolorous", "doltish", "dolts", "domain", "domicile", "dominance", "dominant", "dominate", "domineer", "donate", "donator", "donee", "donkey", "donor", "doodled", "doomsayer", "door", "doorpost", "dormant", "dorsal", "dosas", "dosed", "dotcom", "dotted", "double", "doublet", "doubling", "doubly", "doubt", "doubted", "doubter", "doughy", "dove", "down", "downer", "downgrade", "downplay", "downpour", "downward", "dowry", "doyen", "doyenne", "doze", "dozens", "dozes", "drabness", "drachma", "draft", "draftees", "drafty", "dragnet", "dragoon", "dragoons", "drain", "drainage", "dramatist", "dramatize", "drape", "drastic", "dream", "drearily", "drenches", "dress", "dressing", "dribble", "drill", "dripping", "drive", "driveway", "driving", "drizzle", "drone", "droning", "drool", "droopy", "droplet", "dropoff", "drought", "drowsy", "drudgery", "druids", "drummer", "drunk", "drunks", "dubbed", "dubious", "duckling", "ductile", "dud", "due", "duel", "duet", "dun", "dung", "dunk", "dupe", "duplex", "duplexer", "duplicity", "durance", "duration", "dusky", "dust", "duster", "dusting", "duteous", "dutiable", "dutiful", "dwarf", "dweeb", "dweller", "dwelling", "dwindle", "dyed", "dynamic", "dynamism", "dyne", "each", "eagerly", "eardrum", "eardrums", "earless", "earlier", "early", "earn", "earnest", "earplug", "earthwork", "eatable", "eave", "ebb", "ebullient", "eccentric", "ecclesia", "echo", "echoing", "eclipse", "ecliptic", "economize", "ecstasy", "ecstatic", "edible", "edict", "edify", "editorial", "educate", "educe", "efface", "effect", "effective", "effectual", "effete", "efficacy", "efficient", "effluvium", "effuse", "effusion", "egghead", "eggnog", "egoism", "egoist", "egotism", "egotist", "egregious", "egress", "egret", "eighteen", "eject", "ejecting", "ejector", "ejido", "ekes", "eland", "elapse", "elastics", "elbow", "eld", "elder", "elegy", "element", "elevation", "elicit", "eligible", "eliminate", "elite", "elixir", "ellagic", "elm", "elms", "elocution", "eloquence", "eloquent", "elucidate", "elude", "elusion", "elusive", "emaciate", "emanate", "embalmed", "embargo", "embark", "embarrass", "embedded", "embellish", "embezzle", "emblazon", "emblem", "embody", "embolden", "embolism", "embrace", "embroil", "emerge", "emergence", "emergent", "emeritus", "emigrant", "emigrate", "eminence", "eminent", "emit", "emitter", "emotions", "empathic", "emperor", "emphasis", "emphasize", "emphatic", "employed", "employee", "employer", "employs", "emporium", "empower", "empowers", "empty", "emulate", "emulated", "enact", "enamor", "encamp", "encased", "enchant", "enclave", "enclosed", "encomium", "encompass", "encore", "encourage", "encroach", "encumber", "endanger", "endear", "endeavor", "ended", "endemic", "endgame", "endlessly", "ends", "endue", "endurable", "endurance", "energetic", "enervate", "enfeeble", "engender", "engrave", "engraved", "engross", "engulfed", "enhance", "enhanced", "enigma", "enjoin", "enjoy", "enjoyment", "enkindle", "enlighten", "enlist", "enliven", "enmity", "ennoble", "enormity", "enormous", "enrage", "enrapture", "enriched", "enroll", "enshrine", "ensnare", "ensnared", "ensue", "ensuing", "entail", "entangle", "enthrall", "enthrone", "enthuse", "enticing", "entirety", "entitled", "entity", "entrails", "entrap", "entreaty", "entree", "entrench", "entrust", "entry", "entwine", "enumerate", "enviable", "envious", "envy", "envying", "ephemera", "epic", "epicure", "epicycle", "epidemic", "epidermis", "epigram", "epilogue", "epiphany", "episode", "episodic", "epitaph", "epithet", "epitome", "epizootic", "epoch", "epode", "eponym", "equaling", "equality", "equalize", "equate", "equitable", "equity", "equivocal", "eradicate", "erect", "errant", "erratic", "erroneous", "error", "erstwhile", "erudite", "erudition", "escapes", "escaping", "escarole", "eschew", "espionage", "espy", "esquire", "essayed", "essence", "essential", "esteemed", "esthetic", "estimable", "estrange", "estrogen", "estuary", "etcetera", "etch", "etiquette", "eugenic", "eulogize", "eulogy", "euphemism", "euphony", "eureka", "evade", "evanesce", "evasion", "even", "evensong", "event", "eventful", "eventual", "ever", "evergreen", "evert", "everyone", "evict", "eviction", "evidence", "evil", "evince", "evincing", "evoke", "evolution", "evolve", "evolves", "examiner", "excavate", "exceed", "excel", "excellent", "excels", "excepting", "excerpt", "excess", "excitable", "excitedly", "exclude", "excluded", "exclusion", "excretion", "excursion", "excusable", "excuse", "execrable", "executive", "executor", "exegesis", "exemplar", "exemplary", "exemplify", "exempt", "exempted", "exert", "exhale", "exhaled", "exhaust", "exhume", "exigency", "exigent", "existence", "existing", "exists", "exit", "exits", "exodus", "exonerate", "exorcise", "exotic", "expand", "expanse", "expansion", "expansive", "expect", "expected", "expedient", "expedite", "expend", "expense", "experts", "expiate", "expired", "explain", "explicate", "explicit", "explode", "exploding", "exploiter", "explosion", "explosive", "export", "exposure", "expulsion", "extant", "extempore", "extend", "extension", "extensive", "extensor", "extenuate", "exterior", "external", "extinct", "extol", "extort", "extortion", "extradite", "extremist", "extremity", "extricate", "extrude", "exuberant", "exurbs", "eyed", "eyelash", "eyelid", "eyesight", "fabled", "fables", "fabricate", "fabulous", "face", "faceless", "facelift", "facet", "facetious", "facial", "facile", "facility", "facsimile", "faction", "factious", "factual", "faculty", "fading", "fail", "faint", "fairly", "faith", "fake", "falcon", "fallacy", "fallback", "fallen", "fallible", "falling", "fallow", "family", "famine", "famish", "famished", "famous", "fanatic", "fancier", "fancies", "fanciless", "fancy", "fanfare", "fanned", "fannies", "far", "farm", "farming", "fart", "fashion", "fastball", "faster", "fatalism", "fathers", "fathom", "fatties", "fatuous", "faulty", "faun", "favor", "fawn", "fax", "faxes", "fealty", "fear", "feasible", "feasted", "feathered", "feature", "features", "febrile", "fecund", "federate", "fedora", "feeling", "feels", "feint", "felicity", "feline", "fell", "fellow", "felon", "felonious", "felony", "female", "feminine", "femoral", "fen", "fenced", "fencing", "fend", "fender", "fernery", "ferns", "ferocious", "ferocity", "ferry", "fervent", "fervid", "fervor", "fessed", "festal", "festive", "fetches", "fete", "fetus", "feudal", "feudalism", "fevers", "fez", "fiasco", "fibrotic", "fibs", "fickle", "fiction", "fiddler", "fidelity", "fiducial", "fief", "fiendish", "fiercely", "fifth", "fig", "figure", "filament", "filberts", "filers", "filet", "fill", "filmed", "filmy", "filtered", "filth", "final", "finale", "finales", "finality", "finally", "finance", "financial", "financier", "finery", "finesse", "finger", "finish", "finisher", "finite", "fireman", "fireplace", "fires", "firm", "first", "fiscal", "fishbone", "fishery", "fishes", "fissure", "fisting", "fit", "fitful", "fitted", "fix", "fixes", "fixture", "fizzles", "fjord", "flag", "flagpole", "flagrant", "flags", "flame", "flamed", "flanker", "flare", "flared", "flash", "flasher", "flatboat", "flaunted", "flavor", "flawless", "fleck", "flection", "fledged", "fledgling", "fleece", "fleeting", "fleshing", "flexible", "flicked", "flicks", "flimsy", "flippant", "flirts", "flit", "floating", "flocked", "floe", "flogged", "floods", "floozie", "flora", "floral", "florid", "florist", "flout", "flowing", "flu", "fluctuate", "flue", "fluent", "fluential", "fluffier", "fluidly", "fluke", "flunk", "flush", "fluted", "flutist", "flux", "fly", "flyer", "focal", "focally", "focus", "foe", "fog", "foggy", "foible", "foist", "folds", "foliage", "folic", "folio", "fond", "fondle", "fondly", "fondness", "foolery", "foolishly", "foolproof", "footed", "foothill", "footless", "footloose", "footprint", "footrest", "foppery", "foppish", "foray", "forby", "forces", "forcible", "fore", "forearm", "forebear", "forebode", "forecast", "foreclose", "forecourt", "forego", "forehead", "foreign", "foreigner", "forejudge", "foreman", "forepeak", "foreplay", "forerun", "foresail", "foresee", "foreshore", "foresight", "forestry", "forests", "foretell", "foretold", "forever", "foreword", "forfeit", "forfend", "forger", "forgery", "forget", "forgiving", "forgo", "fork", "forkful", "forklift", "format", "formation", "former", "formula", "forsakes", "forswear", "fort", "forte", "forth", "fortify", "fortitude", "fortnight", "forum", "forwards", "fostered", "fourfold", "foursome", "fourth", "fracture", "fragile", "frail", "frailty", "frame", "franchise", "frantic", "frat", "fraternal", "fraud", "fraught", "fray", "frazzle", "freak", "freaking", "freakish", "freaks", "free", "freed", "freelance", "freely", "freemason", "freeplay", "freesia", "freezer", "freight", "frenetic", "frequency", "fresco", "freshness", "fret", "fretful", "fries", "frigging", "fright", "frighten", "frightful", "frigid", "frilled", "frills", "fringe", "frivolity", "frivolous", "frizz", "frizzle", "frizzy", "frontier", "frostbite", "frowzy", "froze", "frozen", "frugal", "frugally", "fruiting", "fruition", "fruity", "frying", "fueled", "fugacious", "fulcrum", "fulminate", "fulsome", "fume", "fumigate", "funds", "funereal", "fungible", "fungous", "fungus", "funk", "funkiest", "funnies", "funny", "furbish", "furious", "furled", "furlong", "furlough", "furnish", "furrier", "further", "furtive", "fusarium", "fuse", "fusible", "fusillade", "fussed", "futile", "futurist", "gabbling", "gabled", "gaffer", "gagged", "gaggle", "gaiety", "gaily", "gained", "gaining", "gait", "gala", "gale", "gallant", "gallantly", "galore", "galvanic", "galvanism", "galvanize", "gamble", "gambol", "game", "gamebird", "gamester", "gamine", "gaming", "gamut", "gardens", "gargle", "garish", "garland", "garlic", "garlicky", "garner", "garnish", "garrison", "garrote", "garrulous", "gaseous", "gastric", "gastritis", "gather", "gathered", "gaudiest", "gaudy", "gauge", "gauges", "gawk", "gawks", "gay", "geared", "geezer", "gemsbok", "gendarme", "genealogy", "generally", "generate", "generic", "genesis", "genetics", "geniality", "genital", "genitive", "genomes", "genteel", "gentile", "gentle", "gentry", "genuine", "geology", "germane", "germinate", "gestalt", "gestation", "gesture", "gestured", "getaway", "geyser", "ghastly", "gherkin", "ghosting", "ghoulish", "giant", "gibbous", "gibe", "giddy", "gift", "gigantic", "ginseng", "girdle", "girlie", "git", "give", "given", "giver", "giving", "glacial", "glacier", "gladden", "gladioli", "glanced", "glances", "glassware", "glazier", "gleam", "glen", "glided", "glimmer", "glimpse", "glints", "glioma", "glittery", "globes", "globose", "globular", "globules", "gloom", "gloried", "glorify", "glorious", "glory", "glow", "glut", "glutinous", "glycemic", "gnash", "goalpost", "goat", "goatee", "goats", "gobble", "godly", "gold", "golly", "goodbye", "goodie", "goofily", "gook", "gopher", "gordian", "gorge", "gory", "gosling", "goslings", "gossamer", "gourd", "gourmand", "gown", "graceless", "gradation", "grade", "graded", "graders", "gradient", "gradual", "graffiti", "grail", "granary", "grand", "grandeur", "grandiose", "grandmom", "grandpa", "granny", "grantee", "grantor", "granular", "granulate", "granule", "grape", "graph", "graphic", "grapple", "grasp", "grasping", "grassy", "gratify", "gratuity", "gravelly", "gravity", "grayed", "graying", "greasier", "green", "greenback", "greeter", "grenadier", "greying", "greyness", "gridlike", "grief", "grievance", "grievous", "grimace", "grind", "grins", "grip", "gripes", "gripped", "grisly", "grit", "groggy", "groom", "groomers", "grope", "groped", "grotesque", "grotto", "ground", "grounding", "group", "grouping", "grousing", "growls", "growths", "grudge", "grueling", "grumble", "grumbled", "grumbling", "grumpy", "grungy", "guard", "guardrail", "guerrilla", "guess", "guessing", "guidance", "guidebook", "guided", "guile", "guileless", "guilty", "guinea", "guise", "gullible", "gumbo", "gummed", "gumption", "gunfight", "gunmetal", "gunships", "gunshot", "gunshots", "gurney", "gust", "gusto", "gusty", "gutter", "guy", "guzzle", "gym", "gyrate", "gyrating", "gyre", "gyroscope", "habitable", "habitant", "habitual", "habitude", "hack", "hackle", "hackney", "hackwork", "haggard", "haggling", "hair", "hairline", "hairpin", "hairy", "halcyon", "hale", "halfback", "halibut", "hallowed", "hammer", "hammering", "hamming", "hamper", "hampers", "handbag", "handball", "handedly", "handily", "handling", "handmade", "handout", "handy", "handyman", "hang", "hanger", "hanker", "hankering", "haole", "happened", "harangue", "harass", "harbinger", "harbour", "hardening", "hardier", "hardihood", "hardly", "harm", "harmless", "harness", "harvest", "harvests", "hashed", "hassle", "hassling", "hastily", "hasty", "hat", "hatch", "hater", "hatless", "haunt", "have", "havoc", "haw", "hawker", "hawking", "hawkish", "hawthorn", "hazard", "hazardous", "haziness", "head", "headed", "headland", "headrest", "heads", "headship", "heady", "heal", "healed", "healthful", "hearken", "hearsay", "hearted", "hearten", "heartless", "heated", "heathen", "heavy", "hectares", "hedge", "hedges", "heedless", "heifer", "height", "heinie", "heinous", "heist", "helicity", "hellcat", "helm", "helo", "helpful", "hen", "henchman", "henpeck", "heptagon", "heptarchy", "her", "herbarium", "hereby", "heredity", "heresy", "heretic", "herewith", "heritage", "hernia", "heroic", "herstory", "hesitancy", "hesitant", "hetero", "heterodox", "hexagon", "hexapod", "heyday", "hiatus", "hibernal", "hickory", "hideaway", "hideous", "hie", "high", "higher", "hightail", "hiker", "hilarious", "hillbilly", "hillock", "hillside", "hinder", "hindmost", "hindrance", "hint", "hints", "hippest", "hirsute", "his", "hissy", "history", "hitherto", "hive", "hmong", "hoard", "hoarse", "hobbit", "hobnob", "hock", "hockey", "hog", "holdover", "hole", "hollowed", "hollowly", "homage", "home", "homed", "homely", "homemade", "homemaker", "homespun", "homework", "homily", "homonym", "homophone", "homos", "honoree", "honorific", "hood", "hoodwink", "hook", "hooks", "hoot", "hopeless", "hopscotch", "horde", "hornless", "horny", "horribly", "hosed", "hosiery", "hosing", "hospice", "host", "hostility", "hot", "hotline", "howitzer", "huckster", "huddled", "huff", "huffed", "hum", "human", "humane", "humanize", "humbling", "humbug", "humiliate", "humming", "hummocks", "humorless", "humping", "hunch", "hunger", "hungry", "hunk", "hunks", "hunt", "hunts", "hurdle", "hurdler", "hurt", "hurting", "hurtle", "husband", "hussar", "hustle", "hustled", "hustlers", "hybrid", "hydra", "hydraulic", "hydride", "hydrous", "hygiene", "hygienist", "hyphal", "hypnosis", "hypnotic", "hypnotism", "hypnotize", "hypocrisy", "hypocrite", "hysteria", "icebox", "icecream", "iceman", "ichthyic", "icily", "iciness", "icon", "icy", "ideal", "idealize", "identify", "idiom", "idolize", "idylls", "ignoble", "ignore", "ignored", "iguana", "ill", "illegal", "illegible", "illiberal", "illicit", "illiquid", "illness", "illogical", "illumine", "illusion", "illusive", "illusory", "image", "imaginary", "imagine", "imago", "imbalance", "imbibe", "imbroglio", "imbrue", "imitation", "imitator", "immature", "immense", "immensity", "immerse", "immersion", "immigrant", "immigrate", "imminence", "imminent", "immodest", "immoral", "immovable", "immune", "immutable", "impact", "impair", "impaired", "impale", "impaling", "impartial", "impassive", "impatient", "impede", "impedes", "impel", "impend", "imperil", "imperiled", "imperious", "impetuous", "impetus", "impiety", "impinges", "impious", "impliable", "implicate", "implicit", "implode", "implore", "imply", "impolite", "impolitic", "important", "importune", "imposed", "impotent", "imprints", "impromptu", "improper", "improved", "improvise", "imprudent", "impudence", "impugn", "impulse", "impulsion", "impulsive", "impunity", "impure", "impurity", "impute", "inability", "inactive", "inane", "inanimate", "inapt", "inaudible", "inboard", "inborn", "inbred", "incentive", "inception", "inceptive", "incessant", "inchmeal", "inchoate", "incidence", "incident", "incipient", "incise", "incisor", "incite", "inciting", "include", "included", "incorrect", "increment", "indelible", "indepth", "index", "indians", "indicant", "indicator", "indict", "indigence", "indigent", "indignant", "indignity", "indolence", "indolent", "indoors", "induct", "induction", "indulgent", "inebriate", "inedible", "ineffable", "inept", "inequity", "inert", "inertia", "infamous", "infamy", "infancy", "infected", "inference", "inferior", "infernal", "infest", "infidel", "infinite", "infinity", "infirm", "infirmary", "infirmity", "inflamed", "inflects", "influence", "influx", "informant", "infringe", "infuse", "infusion", "ingenious", "ingenuity", "ingenuous", "ingraft", "ingrate", "inhabited", "inherence", "inherent", "inhibit", "inhuman", "inhume", "inimical", "iniquity", "initial", "initiate", "inject", "injustice", "inkling", "inky", "inland", "inlay", "inlet", "inmost", "innards", "innocuous", "innovate", "innuendo", "inquire", "inquiry", "inroad", "inroads", "inscribe", "insecure", "insertion", "inside", "insidious", "insight", "insinuate", "insipid", "insistent", "insolence", "insolent", "insomnia", "insomniac", "inspect", "inspector", "inspiring", "instance", "instant", "instigate", "instill", "insular", "insulate", "insult", "insulted", "insure", "insured", "insurgent", "intake", "integrity", "intellect", "intensely", "intension", "intensive", "intention", "interact", "intercede", "intercept", "interdict", "interface", "interim", "interior", "interlude", "intermit", "intermix", "interplay", "interpose", "interpret", "interrupt", "intersect", "interval", "intervale", "intervene", "interview", "intestacy", "intestate", "intestine", "intimacy", "intrepid", "intricacy", "intricate", "intrigue", "intrinsic", "intromit", "introvert", "intrude", "intrusion", "intrusive", "intubate", "intuition", "inundate", "inure", "invades", "invalid", "invasion", "invective", "inveigh", "inventive", "inverse", "inversion", "invert", "investing", "investor", "invidious", "invitee", "invoke", "involve", "inward", "inwardly", "ionized", "iota", "ipods", "irascible", "irate", "ire", "irk", "irksome", "ironclad", "ironed", "ironing", "irony", "irradiate", "irrigant", "irrigate", "irritable", "irritancy", "irritant", "irritate", "irruption", "isle", "islet", "isobar", "isolate", "isolated", "issuers", "issuing", "italic", "itch", "itches", "itemized", "itinerant", "itinerary", "itinerate", "jab", "jacked", "jail", "jailing", "jangle", "jar", "jargon", "jarring", "jaundice", "jauntily", "jawed", "jazz", "jazzy", "jealousy", "jeans", "jelly", "jested", "jewel", "jibe", "jiffy", "jiggle", "jiggles", "jigs", "jilted", "jingled", "jingoism", "jinx", "jockey", "jocks", "jocose", "jocular", "jogger", "joggle", "join", "joining", "joins", "joke", "jolly", "jostle", "jot", "joust", "jousting", "jovial", "juche", "judgment", "judicial", "judiciary", "judicious", "juggle", "jugglery", "jugular", "juice", "juicy", "juke", "jumble", "jumbled", "jump", "jumpsuit", "junction", "juncture", "junior", "juniper", "junker", "junket", "junky", "junta", "juntas", "juridical", "juristic", "juror", "jutting", "juvenile", "juxtapose", "kales", "kaput", "karate", "karma", "keen", "keeps", "keepsake", "kerchief", "kernel", "kerygma", "keystone", "khaki", "khakis", "kibbles", "kickball", "killing", "kiln", "kiloliter", "kilometer", "kilowatt", "kilted", "kimono", "kind", "kingling", "kingship", "kinking", "kinks", "kinsfolk", "kinsman", "kirtle", "kissed", "kitty", "knavery", "knead", "kneecaps", "kneejerk", "knife", "knight", "knitting", "knocked", "knot", "knotty", "knower", "knowingly", "knowledge", "knuckled", "kumbaya", "labeled", "labelled", "labial", "laborious", "labyrinth", "lace", "lacerate", "lack", "lactation", "lacteal", "lactic", "laddie", "ladle", "ladybugs", "laggard", "lags", "laid", "lake", "lambast", "lame", "lance", "landform", "landlord", "landmark", "landscape", "languid", "languor", "lap", "lapdog", "lapel", "lapse", "larder", "larders", "lark", "lash", "lashed", "lassie", "lassies", "lately", "latency", "latent", "later", "lateral", "latish", "lattice", "laud", "laudable", "laudation", "laudatory", "laughably", "laundress", "laureate", "lave", "lavish", "law", "lawfully", "lawgiver", "lawmaker", "lawsuit", "lax", "laxative", "lay", "laydown", "layman", "laze", "laziness", "lazy", "lea", "leadeth", "leaflet", "leafy", "leak", "leaked", "leaker", "leakers", "leaky", "leapers", "least", "leaven", "leaver", "lebanese", "leech", "leeward", "leftism", "legacy", "legalize", "legalized", "legally", "legging", "legible", "legionary", "legislate", "legume", "leisure", "lend", "lenders", "leniency", "lenient", "lens", "leonine", "leopard", "leprosy", "less", "lessen", "lest", "lethargy", "lettered", "leukemia", "levee", "lever", "leverage", "leviathan", "levity", "levy", "lewd", "lexicon", "liable", "liaisons", "libel", "liberal", "liberate", "librarian", "library", "licensed", "licit", "lick", "licorice", "liege", "lien", "lies", "lieu", "lifeless", "lifelike", "lifelong", "lifes", "lifetime", "ligament", "ligature", "light", "lightbulb", "lighter", "ligneous", "lik", "like", "likely", "liken", "likened", "likewise", "liking", "lilt", "liminal", "limousine", "limp", "limply", "limpness", "limps", "linear", "liner", "lingered", "lingo", "lingua", "lingual", "linguist", "liniment", "linseed", "lip", "liquefy", "liqueur", "liquidate", "liquor", "listed", "listen", "listened", "listless", "lite", "literacy", "literal", "literally", "literary", "lithe", "lithesome", "lithotype", "litigant", "litigate", "litigious", "litter", "littoral", "liturgy", "live", "liven", "livened", "liver", "livid", "loaf", "loam", "loamy", "loans", "loath", "loathe", "lobotomy", "local", "localized", "located", "locative", "loch", "loci", "lockers", "lockouts", "locks", "lode", "lodge", "lodgepole", "lodgers", "lodgment", "lofty", "logger", "logic", "logical", "logically", "logician", "loiterer", "lollipop", "long", "longer", "longevity", "longingly", "longitude", "looked", "loosely", "loosen", "loot", "lopping", "lordling", "lore", "losing", "loss", "lot", "lotus", "lough", "lounge", "lounges", "louse", "lousy", "lout", "louts", "lovable", "lovably", "loveable", "lovelorn", "loving", "lower", "lowly", "loyalist", "loyally", "loyalty", "lubricate", "lucid", "lucrative", "ludicrous", "lug", "lugged", "lukewarm", "lull", "lumber", "luminary", "luminous", "lunacy", "lunar", "lunatic", "lunch", "lune", "lupine", "lupines", "lurid", "lurking", "luscious", "lustful", "lustrous", "luxuriant", "luxuriate", "luxury", "lye", "lying", "lynch", "lyre", "lyres", "lyric", "ma'am", "machinery", "machinist", "mackerel", "macrocosm", "madden", "maestro", "magenta", "magic", "magical", "magician", "maglev", "magnate", "magnet", "magnetize", "magnitude", "maharaja", "mailbox", "mailing", "main", "mainline", "maintain", "maize", "makeshift", "makeup", "malady", "malaria", "malawian", "malay", "malign", "malignant", "malleable", "mallet", "malt", "malted", "maltreat", "mambo", "mammoth", "managed", "mandarin", "mandate", "mandated", "mandatory", "mane", "maneuver", "manger", "mangled", "mangos", "mangy", "manhole", "manhunt", "mania", "maniac", "manifest", "manifesto", "manlike", "manliness", "manna", "manned", "mannerism", "manor", "mans", "mantel", "mantle", "manumit", "map", "mappings", "marble", "marbled", "march", "marchers", "marijuana", "marinade", "marine", "maritime", "marked", "markers", "markings", "marksman", "marmalade", "maroon", "married", "marry", "martial", "martian", "martinis", "martyrdom", "marvel", "marxism", "mascara", "masked", "masonry", "massacre", "massage", "masses", "masseurs", "massive", "mastery", "mastiffs", "mastoid", "matched", "material", "maternal", "maternity", "matinee", "matricide", "matrimony", "matrix", "matter", "mature", "maudlin", "mausoleum", "maverick", "mawkish", "maxim", "maximize", "may", "maze", "mead", "meager", "meagerly", "mean", "meander", "meanies", "means", "measured", "measures", "mechanics", "medallion", "meddling", "medial", "mediate", "medicate", "medicine", "medieval", "mediocre", "meditate", "medium", "medley", "meek", "melded", "meliorate", "mellow", "melodious", "melodrama", "melted", "memento", "memorable", "men", "menace", "menagerie", "mend", "mendicant", "menfolk", "mental", "mentality", "mentally", "menthol", "mention", "mentor", "mentored", "mercenary", "merciful", "merciless", "mere", "merge", "merited", "mesmerize", "messed", "messieurs", "messing", "mestizo", "metal", "metaphor", "mete", "metonymy", "metric", "metronome", "mettle", "micro", "microchip", "microcosm", "middleman", "midges", "midlife", "midline", "midpoint", "midriff", "midriffs", "midsole", "midsummer", "midwife", "mien", "might", "mignon", "migrant", "migrate", "migration", "migratory", "mikes", "mil", "mileage", "miles", "milieu", "militant", "militate", "militia", "milk", "milking", "milky", "millet", "milling", "millionth", "mime", "mimic", "mimics", "minaret", "minces", "mind", "mindless", "mined", "mineral", "mingle", "miniature", "minimize", "minion", "ministry", "minivans", "minority", "minuet", "minus", "minute", "minutia", "mirage", "misbehave", "miscount", "miscreant", "misdeed", "miser", "miserly", "misfire", "misfit", "mishap", "mishmash", "mislaid", "mislay", "mismanage", "misnomer", "misogamy", "misogyny", "misplace", "misplaced", "misrule", "missal", "missile", "missive", "mistily", "mistrust", "misty", "misuse", "mite", "miter", "mitigate", "mixed", "mixture", "mnemonics", "moat", "mobocracy", "moccasin", "mocha", "mock", "mockery", "model", "moderate", "moderator", "modernity", "modernize", "modify", "modish", "modulate", "moduli", "moistly", "molar", "molecule", "molehill", "mollify", "molt", "momentary", "momentous", "momentum", "monarch", "monarchy", "monastery", "monetary", "monger", "mongrel", "monied", "monition", "monitory", "monks", "monocracy", "monogamy", "monogram", "monograph", "monolith", "monologue", "monomania", "monopoly", "monotone", "monotony", "monsieur", "monsoon", "monster", "mood", "moonbeam", "moonlit", "moored", "mooring", "mop", "moral", "morale", "moralist", "morality", "moralize", "moray", "morbid", "mordant", "more", "moreover", "morgue", "moribund", "mormon", "morn", "morning", "morose", "morph", "morsel", "mortar", "mortician", "mortise", "mosaic", "moslems", "motley", "motor", "motorcar", "motorist", "motto", "mound", "mounted", "mounting", "mourn", "mournful", "mouser", "mouth", "mouthed", "mouthful", "mouton", "move", "movement", "mower", "muddle", "muddled", "muffle", "muffler", "mug", "mulatto", "muleteer", "mull", "mullah", "multiform", "multiplex", "multiply", "mundane", "municipal", "murres", "muscle", "music", "musicale", "musically", "muskie", "mussels", "mustache", "muster", "mutagens", "mutation", "mute", "muted", "mutilate", "mutilated", "mutiny", "muzzling", "myriad", "mystic", "mystical", "myth", "mythology", "nabbed", "naked", "nameless", "namibian", "naming", "nanny", "nap", "naphtha", "narcos", "narrate", "narration", "narrative", "narrator", "narrow", "narrowed", "narrowly", "nasal", "nascent", "natal", "national", "native", "natty", "naturally", "naught", "naughty", "nausea", "nauseate", "nauseous", "nautical", "naval", "nave", "navel", "navies", "navigable", "navigate", "nay", "nearing", "nearside", "neaten", "neatnik", "nebula", "necessary", "necessity", "neck", "necklace", "necktie", "necrology", "necropsy", "necrosis", "nectar", "nectarine", "needed", "needy", "nefarious", "negate", "negation", "negligee", "negligent", "neighbor", "neocracy", "neology", "neophyte", "nerdy", "nestle", "nestled", "nestling", "nests", "net", "netball", "nettle", "network", "neural", "neurology", "neuter", "neutered", "neutral", "newborn", "newcomer", "news", "newsman", "newsstand", "next", "nexus", "nicety", "nicked", "nigerian", "niggardly", "nightmare", "nihilist", "nil", "nimble", "nine", "nit", "nitrate", "nitride", "nobody", "nocked", "nocturnal", "noise", "noiseless", "noisome", "noisy", "nomad", "nomads", "nomic", "nominal", "nominate", "nominee", "nonentity", "nonfarm", "nonmember", "nonpareil", "nonsense", "nonstop", "noon", "noose", "nope", "norm", "normalcy", "normally", "norse", "northern", "nosegay", "nostalgia", "nostalgic", "nostrum", "not", "notch", "notched", "nothing", "notion", "notorious", "novelty", "novice", "nowadays", "nowhere", "noxious", "nuance", "nub", "nubbly", "nubians", "nucleon", "nucleus", "nude", "nudge", "nudity", "nugatory", "nuggets", "nuisance", "nuking", "numeracy", "numerical", "numerous", "nunnery", "nuptial", "nurse", "nurture", "nurtured", "nutriment", "nutritive", "nuttier", "oaken", "oaks", "oakum", "oar", "obdurate", "obeisance", "obelisk", "obese", "obesity", "obey", "obituary", "objection", "objective", "objector", "obligate", "oblique", "oblivion", "oblong", "obnoxious", "obsequies", "observant", "obsolete", "obstinacy", "obstruct", "obtrude", "obtrusive", "obtuse", "obvert", "obviate", "occasion", "occlude", "occult", "occupant", "occupied", "occur", "oceanic", "ochre", "octagon", "octave", "octavo", "octet", "ocular", "oculist", "oddballs", "oddity", "ode", "odious", "odium", "odorants", "odorous", "oeuvre", "off", "offed", "offended", "offensive", "offhand", "officiate", "officious", "offload", "offshoot", "offsides", "ogre", "oil", "oiled", "ointment", "okapi", "okay", "oke", "oldie", "oldies", "olfactory", "omega", "omelette", "omen", "omicron", "ominous", "omission", "once", "oncology", "onerous", "ongoing", "onion", "online", "onrush", "onset", "onslaught", "onstage", "onus", "ooze", "opals", "opaque", "open", "opened", "openness", "operant", "operate", "operative", "operator", "operetta", "opinion", "opinions", "opium", "opponent", "opportune", "opposing", "opposite", "oppress", "optic", "optician", "optics", "optimism", "optimist", "option", "optometry", "opulence", "opulent", "oracular", "oral", "orange", "orate", "oration", "orator", "oratorio", "oratory", "orb", "orbiting", "ordains", "ordeal", "order", "ordinal", "ordinary", "ordnance", "ore", "orgies", "origin", "original", "originate", "ornate", "ornately", "orphanage", "orthodox", "orthodoxy", "oscillate", "osculate", "osmosis", "osprey", "ossify", "ostracism", "ostracize", "ostrich", "other", "others", "otter", "ought", "oust", "out", "outback", "outbreak", "outburst", "outcast", "outcome", "outcry", "outdo", "outdoor", "outfit", "outfox", "outlast", "outlasts", "outlaw", "outlawed", "outlay", "outlive", "outpaced", "outpost", "outrage", "outraged", "outreach", "outride", "outrigger", "outright", "outset", "outside", "outskirt", "outstrip", "outward", "outweigh", "oval", "ovary", "over", "overcome", "overdo", "overdose", "overeat", "overhang", "overhead", "overjoyed", "overlap", "overleap", "overload", "overlook", "overlord", "overpass", "overpay", "overpower", "overreach", "overrun", "oversee", "overseer", "oversold", "overtax", "overthrow", "overtime", "overtone", "overtook", "overture", "overview", "owner", "pacify", "package", "packet", "pact", "padding", "paddle", "pagan", "pageant", "paid", "pain", "painfully", "painting", "pair", "palate", "palatial", "palest", "palette", "palinode", "pall", "pallet", "palliate", "pallid", "palm", "palpable", "palsy", "paly", "pamper", "pamphlet", "panacea", "pandemic", "panegyric", "panel", "pangs", "panic", "panicky", "panoply", "panoptic", "panorama", "pantheism", "pantomime", "papacy", "papists", "papyri", "papyrus", "par", "parable", "parabolic", "parachute", "paradise", "paradox", "paragon", "parallel", "paralysis", "paralyze", "paramount", "paramour", "paranoid", "parasite", "parched", "pardons", "pare", "parentage", "parenting", "pares", "parfaits", "parietal", "parish", "parisian", "parities", "parity", "parlance", "parlay", "parley", "parlor", "parody", "paroxysm", "parricide", "parrot", "parry", "parse", "parsing", "part", "partible", "partisan", "partition", "parts", "pass", "passible", "passing", "passive", "past", "pastoral", "pat", "patched", "patent", "paternal", "paternity", "pathos", "patiently", "patina", "patriarch", "patrician", "patrimony", "patriot", "patriots", "patronize", "patter", "patting", "paucity", "pauper", "pauperism", "pave", "pavilion", "pawn", "pawned", "pawnshop", "payable", "payee", "peaceable", "peaceful", "pearl", "pec", "peccable", "peccant", "pectoral", "pecuniary", "pedagogue", "pedagogy", "pedal", "pedant", "peddle", "peddler", "pedestal", "pedigree", "peek", "peeks", "peels", "peep", "peer", "peerage", "peerless", "peeve", "peeved", "peeves", "peevish", "peg", "pegboard", "pelagic", "pellagra", "pellucid", "penalty", "penance", "penchant", "pendant", "pendants", "pending", "pendulous", "pendulum", "penetrate", "penitence", "penitent", "pennant", "penned", "pension", "pentad", "pentagon", "pentagram", "penthouse", "penurious", "penury", "peopled", "per", "perceive", "percolate", "perennial", "perfidy", "perforate", "perform", "perfumery", "perhaps", "perigee", "perineal", "perjure", "perjury", "permanent", "permeate", "permit", "peroxide", "perp", "persevere", "persist", "personage", "personal", "personify", "personnel", "perspire", "persuade", "pertinent", "perturb", "perusal", "pervade", "pervasion", "pervasive", "perverse", "pervert", "pervious", "pesky", "pestilent", "peter", "petrify", "petty", "petulance", "petulant", "pewter", "pharmacy", "phase", "philander", "philately", "philology", "phonemic", "phonetic", "phonic", "phonogram", "phonology", "phony", "physicist", "physics", "physique", "pianists", "pianos", "piazzas", "picayune", "piccolo", "picked", "picket", "picky", "piddling", "piece", "piecemeal", "pier", "piercing", "piety", "pig", "pigeon", "pile", "pillage", "pillaged", "pillar", "pillbox", "pillory", "pillowy", "pilot", "pimp", "pimped", "pin", "pincers", "pinchers", "pine", "pinecone", "pinheads", "pinions", "pinker", "pinnacle", "pinochle", "pinot", "pinstripe", "pinup", "pioneer", "pious", "pipa", "pipe", "pipefish", "piping", "pique", "piracy", "piranha", "piss", "pissing", "piste", "pistils", "pistol", "piston", "pitch", "pitchers", "pitching", "piteous", "pith", "pitiable", "pitiful", "pitifully", "pitiless", "pitot", "pittance", "pivoting", "pizza", "pizzas", "placate", "placid", "plagued", "plait", "plan", "plant", "platitude", "plaudit", "plausible", "playback", "playful", "playfully", "playing", "plea", "plead", "pleasant", "pleasing", "pleated", "plebeian", "pled", "pledgee", "pledgeor", "plenary", "plenitude", "plenteous", "plenty", "plethora", "pleura", "pliant", "plight", "plod", "plot", "plotted", "plover", "plowman", "pluck", "plug", "plumb", "plumber", "plume", "plumed", "plummet", "plump", "plunder", "plunger", "plunked", "plural", "plurality", "plusses", "plutonium", "pneumatic", "poacher", "pocked", "pocketed", "poesy", "poet", "poetaster", "poetic", "poetics", "poignancy", "poignant", "point", "poise", "poison", "poisoned", "poke", "polar", "polarity", "polemics", "police", "policy", "polish", "polished", "politic", "politics", "polled", "pollen", "pollute", "polluter", "polyarchy", "polycracy", "polygamy", "polyglot", "polygon", "pommel", "pomposity", "pompous", "poncho", "ponder", "ponderous", "ponds", "pontiff", "pontiffs", "pontoon", "poof", "pooled", "pooping", "poorly", "populace", "popularly", "populist", "populous", "pored", "porous", "port", "portable", "portend", "portent", "portfolio", "portion", "pose", "posit", "position", "positive", "posse", "posses", "possess", "possessor", "possible", "possibly", "postdate", "poster", "posterior", "postwar", "potassium", "potency", "potent", "potentate", "potential", "potholed", "potion", "potted", "pounding", "powder", "powdered", "powerless", "practiced", "praising", "prancing", "prate", "prattle", "pray", "preacher", "preachy", "preamble", "precede", "precedent", "precipice", "precise", "precision", "preclude", "precursor", "precut", "predatory", "predicate", "predict", "preempt", "preempts", "preengage", "preexist", "preface", "prefatory", "prefect", "prefer", "prefers", "prefix", "prejudice", "prelacy", "prelate", "prelude", "premature", "premier", "premise", "premium", "premiums", "prenatal", "preoccupy", "preordain", "prepaid", "presage", "presages", "preschool", "prescient", "prescript", "preserve", "presided", "pretend", "pretender", "pretest", "pretext", "pretty", "pretzel", "prevalent", "preview", "prey", "prickle", "pride", "prided", "priests", "priggish", "prim", "prima", "primate", "prime", "primer", "primeval", "primitive", "primrose", "principal", "principle", "print", "printer", "priory", "prisms", "pristine", "private", "privateer", "privilege", "privity", "privy", "prized", "prizing", "pro", "proactive", "probate", "probation", "probe", "probes", "probity", "procedure", "proceed", "proceeds", "processed", "proclaim", "proctor", "prodigal", "prodigy", "producer", "producing", "profane", "professor", "proffer", "profile", "profiteer", "profs", "profuse", "progeny", "program", "project", "projector", "prolific", "prolix", "prologue", "prolong", "prolonged", "prom", "promenade", "prominent", "promise", "promised", "promoter", "prompting", "promptly", "prone", "proofread", "propagate", "propane", "propel", "propeller", "property", "prophecy", "prophesy", "proposed", "proposes", "propound", "propriety", "prorated", "prosaic", "proscribe", "proselyte", "prosody", "prospect", "prostrate", "protean", "protect", "protector", "protege", "protester", "protocol", "prototype", "protract", "protrude", "proven", "proverb", "provident", "provider", "proviso", "provokes", "prow", "prowess", "prowl", "proxy", "prudence", "prudery", "prudish", "pruned", "prurient", "pseudo", "pseudonym", "psychic", "psychotic", "psychs", "puberty", "pudgy", "puerile", "puffs", "puissant", "pullback", "pulley", "pulling", "pulmonary", "pumas", "punches", "punctual", "puncture", "punctured", "pungency", "pungent", "punish", "punished", "punishing", "punitive", "punt", "pupa", "pupilage", "purchase", "purchased", "purebred", "purgatory", "puritan", "purity", "purl", "purloin", "purport", "purpose", "purrs", "pursued", "pursuer", "purveyor", "push", "pushcart", "pushers", "puzzle", "puzzles", "pyre", "pyromania", "pyx", "quackery", "quad", "quadrant", "quadrate", "quadruple", "quaint", "quainter", "quaintly", "qualify", "qualm", "quandary", "quant", "quantity", "quarrel", "quarter", "quarterly", "quartet", "quarto", "quartz", "quay", "queerest", "quelled", "queried", "querulous", "query", "question", "queue", "quibble", "quick", "quickly", "quiescent", "quiet", "quietism", "quietly", "quietus", "quills", "quintet", "quirk", "quit", "quite", "quixotic", "quoted", "rabbis", "rabid", "raccoons", "racist", "racy", "radiance", "radiate", "radical", "radiology", "radix", "raffle", "raft", "rage", "ragged", "raging", "raid", "raillery", "rainy", "raise", "rake", "raked", "ram", "rambler", "ramified", "ramify", "ramose", "rampaging", "rampant", "rampart", "ramparts", "rancor", "range", "rank", "ranked", "rankest", "rankle", "rankled", "ranks", "rap", "rapacious", "rapid", "rapine", "rapper", "rappers", "rapt", "raptorial", "rapturous", "rarebit", "rasa", "rash", "rasping", "raspy", "rat", "rated", "rather", "ratified", "ration", "rational", "rationed", "ratted", "rattles", "ratty", "raucous", "raunchy", "ravage", "ravaged", "ravenous", "raves", "ravine", "raving", "ravish", "raw", "reachable", "reactant", "reaction", "readable", "readily", "readjust", "ready", "realism", "realist", "realize", "realizes", "realm", "realtime", "realtor", "reamer", "reaper", "rear", "rearmed", "rearrange", "reasoned", "reassign", "reassure", "reawaken", "rebelled", "rebounds", "rebuff", "rebuffs", "rebuild", "rebuilt", "rebuke", "rebut", "rec", "recant", "recapture", "recast", "recede", "receding", "receive", "recent", "receptive", "recessive", "reck", "reckless", "reckons", "reclaim", "recline", "recluse", "reclusive", "reclusory", "recognize", "recoil", "recollect", "recording", "recouped", "recourse", "recover", "recreant", "recreate", "recruit", "rectify", "rectitude", "recur", "recure", "recurrent", "recused", "redder", "redeploy", "redesign", "redolence", "redolent", "redound", "redress", "reducible", "redundant", "reef", "refer", "referable", "referee", "refereed", "reference", "referrer", "refigure", "refinery", "reflected", "reflector", "reform", "reformer", "refract", "refrains", "refresh", "refusal", "refused", "refute", "refutes", "regal", "regale", "regalia", "regality", "regent", "regicide", "regime", "regimen", "regiment", "regnant", "regress", "regretful", "regularly", "rehabbed", "rehash", "rehearse", "reign", "reimburse", "rein", "reindeer", "reinstate", "reiterate", "reject", "rejected", "rejection", "rejoin", "relapse", "relapses", "relaxed", "relay", "relaying", "released", "relegate", "relent", "relevant", "reliable", "reliance", "reliant", "relies", "relieved", "reliquary", "relish", "reload", "reluctant", "remanded", "remarry", "remedy", "remiss", "remission", "remodel", "remotely", "renames", "render", "rendition", "renege", "renewal", "renovate", "renovated", "rent", "rents", "reopening", "repaint", "repair", "repairman", "reparable", "repartee", "repeal", "repel", "repellent", "repertory", "repine", "replace", "replay", "replenish", "replete", "replica", "replied", "reply", "report", "reports", "reposed", "reprehend", "repress", "repressed", "reprieve", "reprimand", "reprisal", "reprise", "reprobate", "reprocess", "reproduce", "reproof", "reptilian", "repudiate", "repugnant", "repulse", "repulsed", "repulsive", "repute", "request", "requested", "requiem", "requisite", "requital", "requite", "rerun", "resale", "rescind", "research", "reseat", "resent", "reserved", "reservoir", "reset", "resettle", "residue", "residuum", "resilient", "resistant", "resistive", "resolved", "resonance", "resonate", "resource", "respect", "respects", "respite", "rested", "restored", "rests", "resulted", "resupply", "resurgent", "retained", "retake", "retaliate", "retarded", "retch", "retched", "retells", "retention", "retest", "retests", "reticence", "reticent", "retinue", "retiring", "retitle", "retort", "retouch", "retrace", "retraces", "retract", "retrench", "retrieve", "retro", "return", "reunion", "reunite", "revealed", "reveler", "revelry", "revenant", "revenue", "revere", "reverend", "reverent", "reverse", "reversed", "reversion", "revert", "reverted", "review", "reviewer", "revile", "revisal", "revise", "revive", "revived", "revoke", "revoked", "revolting", "revolved", "revulsion", "rewarm", "rework", "rezoned", "rhapsodic", "rhapsody", "rhetoric", "rhinitis", "rhyme", "ribald", "ribbed", "ricochet", "riddance", "ridge", "ridicule", "riding", "rife", "riff", "riffing", "rig", "rightful", "rights", "rigidity", "rigmarole", "rigor", "rigorous", "rilles", "ringworm", "rip", "ripeness", "ripping", "ripple", "ripplet", "riser", "risible", "rituals", "ritzy", "rivalry", "riverbed", "rivets", "rivulet", "roadblock", "roast", "robe", "robot", "robust", "robusta", "rockers", "rodeo", "rogue", "roll", "roller", "rolloff", "rollouts", "romanced", "rondo", "rookery", "roommate", "rooms", "roosted", "root", "rope", "ropes", "rose", "rosemary", "rotary", "rotate", "rote", "rotten", "rotting", "rotund", "roughy", "round", "roundel", "rounding", "roundup", "roused", "rout", "routed", "routs", "rowdy", "royal", "royally", "rubato", "rubber", "rubric", "ruching", "ruckus", "rue", "ruffian", "ruffle", "ruffled", "rugby", "ruled", "ruminant", "ruminate", "rumor", "runaway", "runny", "runs", "rupee", "rupture", "rural", "rushed", "rust", "rustic", "rusts", "rusty", "rut", "ruth", "ruthless", "rutting", "saber", "sabre", "sac", "sack", "sacred", "sacrifice", "sacrilege", "sad", "saddle", "safari", "safaris", "safeguard", "safest", "sagacious", "sage", "sahelian", "sailor", "sainted", "salable", "salacious", "salami", "salary", "salience", "salient", "saline", "saltine", "salutary", "salvage", "salve", "salvo", "salvos", "same", "sameness", "sanction", "sanctity", "sand", "sandwich", "sane", "sanguine", "sap", "sapid", "sapience", "sapient", "saps", "sarcasm", "sardonic", "sari", "saris", "sartorial", "sashay", "sashays", "satiate", "satire", "satiric", "satirize", "saturate", "satyr", "sauce", "savage", "savaged", "savagely", "save", "savior", "savor", "savour", "saxon", "say", "scabbard", "scabs", "scalawag", "scale", "scaled", "scamming", "scams", "scapegoat", "scarcely", "scarcity", "scarfed", "scarier", "scarily", "scarred", "scat", "scenery", "scented", "schemed", "scholarly", "school", "schooled", "schooling", "schools", "science", "scimitar", "scintilla", "scoffed", "scofflaw", "scolded", "scone", "scoop", "scooting", "scope", "scoped", "scorching", "score", "scorer", "scoring", "scorn", "scorned", "scottish", "scoundrel", "scourged", "scouted", "scouting", "scowls", "scraggly", "screamer", "screeds", "screened", "screw", "scribble", "scribe", "scrim", "scrimp", "scrimped", "scrip", "script", "scrod", "scrubbed", "scrunch", "scruple", "scrutiny", "scum", "scuttle", "scuttled", "scythe", "seal", "sealed", "seance", "seaport", "seaports", "sear", "search", "season", "seasonal", "seatings", "seaweeds", "sebaceous", "secant", "secede", "seceded", "secession", "seclude", "seclusion", "secondary", "secondly", "secrecy", "secretary", "secrete", "secretive", "sect", "section", "secure", "sedate", "sedentary", "sediment", "sedition", "seditious", "seduce", "sedulous", "seeking", "seer", "seethe", "segment", "seignior", "seize", "seized", "selective", "selfish", "seller", "semblance", "seminar", "seminary", "senator", "senile", "senility", "seniority", "sensation", "sense", "sensible", "sensitive", "sensorium", "sensual", "sensuous", "sentence", "sentience", "sentient", "sentinel", "separable", "separate", "sepulcher", "sequel", "sequence", "sequent", "sequester", "serapes", "serenade", "sergeant", "serial", "seriously", "servant", "service", "servitude", "set", "setup", "sever", "severance", "severely", "severity", "sewing", "sexist", "sextet", "sextuple", "shabbily", "shade", "shades", "shading", "shaggy", "shaken", "shakeout", "shaker", "shakes", "shaky", "shallot", "shambles", "shame", "shanks", "shape", "shapely", "sharing", "sharpener", "sheaf", "sheath", "sheer", "shelf", "shell", "shellfish", "shelling", "shelter", "sherbet", "shift", "shiftless", "shimmied", "shimmy", "shin", "shingled", "ship", "shipwreck", "shirked", "shocking", "shod", "shoehorn", "shoot", "shootout", "shored", "shoreline", "short", "shortage", "shortly", "shotgun", "shoulder", "shove", "show", "showcase", "shower", "showman", "showpiece", "showtime", "shred", "shrewd", "shrewdly", "shriek", "shrink", "shrinkage", "shrivel", "shrubbery", "shrug", "shrugs", "shrunken", "shucks", "shuffle", "shutdown", "shutouts", "shutters", "shylock", "shyness", "sibilance", "sibilant", "sibilate", "sibling", "sick", "sickle", "sickness", "sickos", "sickout", "sidelong", "sidereal", "sidling", "siege", "sieges", "sieving", "sift", "sifted", "sighed", "sight", "sightseer", "signup", "silence", "silenced", "silkworm", "sill", "silly", "silos", "silt", "similar", "simile", "simple", "simplify", "simulate", "sinecure", "sinewy", "sinful", "sinfully", "singe", "singeing", "single", "sinister", "sinuosity", "sinuous", "sinus", "sir", "siren", "sirocco", "sit", "site", "situate", "sixteen", "sixteenth", "sixth", "size", "sizzled", "skald", "skate", "skater", "skeleton", "skeptic", "sketch", "skew", "skewed", "skewness", "skiff", "skim", "skimmed", "skirmish", "sky", "skycap", "skyways", "slack", "slain", "slash", "slashed", "slave", "slayer", "sleaze", "sleazy", "sledge", "sleeps", "sleighs", "sleight", "slew", "slews", "slider", "slight", "slimmed", "slimy", "sling", "slippage", "slipped", "slipping", "slob", "slobby", "slosh", "slothful", "slovaks", "slow", "sluggard", "slugger", "slugging", "slum", "slunk", "slurped", "slurred", "slut", "sly", "smacks", "small", "smartly", "smash", "smashed", "smeared", "smell", "smelting", "smock", "smoke", "smuggled", "smugly", "snack", "snacked", "snags", "snapped", "snarl", "snatch", "sneak", "sneeze", "snide", "snipe", "snippet", "snob", "snook", "snooze", "snorer", "snorts", "snowbank", "snowman", "snowmen", "snowy", "snuggle", "soaked", "soap", "soapy", "sobbed", "soberly", "sobriquet", "sociable", "socialism", "socialist", "socials", "sociology", "sodas", "sodomize", "software", "soil", "solace", "solar", "solder", "soldier", "soldiers", "sole", "solecism", "solemnly", "solicitor", "solidity", "soliloquy", "solitary", "solstice", "soluble", "solvable", "solvent", "somatic", "somber", "somebody", "somnolent", "sonata", "sonic", "sonnet", "sonorous", "soot", "soothes", "sop", "sophism", "sophistry", "soprano", "sorbet", "sorcery", "sordid", "sought", "soulmate", "sound", "sounds", "sourwood", "southern", "souvenir", "sow", "space", "spaceship", "spacing", "spacious", "spackle", "spade", "spake", "span", "spangled", "spank", "spar", "spark", "sparkly", "sparring", "sparse", "spasm", "spasmodic", "spatula", "spatulas", "spawn", "spawns", "spear", "special", "specialty", "specie", "species", "specific", "specify", "specimen", "specious", "speckled", "spectator", "specter", "spectres", "spectrum", "speculate", "speedily", "speeds", "spending", "spheroid", "spiced", "spiffy", "spigots", "spike", "spin", "spinous", "spinster", "spiraled", "spires", "spitball", "splashy", "splotch", "splurge", "sponsor", "spook", "spooling", "spoons", "sporting", "spot", "spotty", "sprayers", "spraying", "sprightly", "sprinkle", "sprint", "sprinted", "sprinter", "spry", "spume", "spurious", "spurned", "sputter", "spy", "squabble", "squadron", "squalid", "squalled", "squander", "squared", "squash", "squashed", "squatter", "squeaks", "squeezed", "squirm", "squish", "squishes", "staccato", "stack", "stacking", "staff", "stages", "stagger", "stagnant", "stagnate", "stagy", "staid", "stain", "stainless", "stair", "stairwell", "stalled", "stallion", "stamina", "stampede", "stance", "stanchion", "standoff", "standout", "stands", "stanza", "star", "starch", "stark", "starker", "starkly", "starless", "starring", "stars", "starter", "starters", "stash", "stashes", "stat", "static", "statics", "statuette", "stature", "statute", "stayers", "staying", "steadies", "steal", "stealth", "steam", "steamy", "steeled", "steep", "steeple", "steeples", "steer", "steering", "stellar", "stem", "stencil", "steppe", "stepwise", "sterling", "sternum", "steroids", "stew", "stick", "sticking", "stiff", "stifle", "stifled", "stigma", "stiletto", "still", "stilts", "stimulant", "stimulate", "stimulus", "stinging", "stingy", "stipend", "stir", "stirs", "stitched", "stock", "stockpile", "stodgy", "stoicism", "stolid", "stomped", "stoners", "stoop", "stop", "stopped", "store", "storyline", "stout", "straddle", "strain", "strait", "strand", "stranded", "stratagem", "stratum", "straw", "stray", "streamlet", "stressed", "stretch", "strewn", "stricken", "striders", "stringent", "stripe", "striped", "stripling", "strobe", "stud", "studded", "studious", "stultify", "stumble", "stumbling", "stunners", "stupidest", "stupidly", "stupor", "styling", "suasion", "suave", "subacid", "subjacent", "subjugate", "submarine", "submerge", "submittal", "subside", "subsidize", "subsist", "subsumes", "subtend", "subtests", "subtle", "subvert", "succeed", "success", "successor", "succinct", "succulent", "succumb", "suction", "sudden", "sue", "suffrage", "suffuse", "suffused", "sugar", "suits", "sulfate", "sulfurous", "sulkily", "sultan", "summary", "summation", "summed", "summited", "sumptuous", "sunbeam", "sunburst", "sunflower", "suns", "sunset", "sunsets", "sunshine", "sunward", "super", "superadd", "superb", "superheat", "supersede", "supervise", "supine", "supplant", "supple", "supply", "supposed", "supposing", "suppress", "supremo", "surcharge", "sureness", "surety", "surf", "surface", "surfeit", "surly", "surmise", "surmount", "surplus", "surprise", "surrender", "surrogate", "surround", "surveyor", "survives", "suspect", "suspects", "suspense", "sutra", "suture", "swab", "swale", "swallowed", "swamped", "swarm", "swarmed", "swarthy", "swat", "swath", "swathe", "swathed", "swatting", "swayed", "swearing", "sweat", "sweeping", "sweeps", "sweets", "swell", "swelled", "swelter", "swerving", "swigs", "swimsuit", "swindle", "swindler", "swinging", "swipe", "swipes", "swirled", "swirling", "switching", "swivel", "swoop", "swooped", "sword", "swore", "sycamore", "sycophant", "syllabic", "syllable", "syllabus", "sylph", "symbolic", "symmetry", "symphonic", "symphony", "symposium", "synagogue", "syndicate", "syndrome", "syneresis", "synergy", "synod", "synonym", "synopsis", "syrupy", "table", "tableau", "tableaus", "tabloid", "taboo", "tacit", "taciturn", "tack", "tacked", "tackle", "taco", "tact", "tactful", "tactician", "tactics", "taffeta", "tahini", "tail", "tailspin", "take", "talcum", "talk", "talking", "tallest", "tally", "talmudic", "tamarind", "tamely", "tan", "tandem", "tangency", "tangent", "tangible", "tango", "tankful", "tanned", "tanners", "tannery", "tantalize", "tape", "tapes", "tapestry", "taping", "taquitos", "tardiness", "target", "tarnish", "tarry", "tart", "tartlets", "tater", "tattler", "tattoos", "taupe", "taut", "tax", "taxation", "taxi", "taxicab", "taxidermy", "tea", "teachable", "teacher", "teal", "tearful", "tearooms", "tears", "teary", "tease", "teaser", "teasingly", "technic", "technique", "tedium", "tee", "teem", "teemed", "teenage", "tees", "teetering", "telco", "telecast", "telegraph", "telemetry", "telepathy", "telephony", "telescope", "teletype", "televise", "telltale", "temblors", "temerity", "temp", "temple", "temples", "temporal", "temporary", "temporize", "tempt", "tempter", "temptress", "tenacious", "tenant", "tendency", "tender", "tendril", "tenet", "tenfold", "tennis", "tenor", "tense", "tensest", "tensions", "tentative", "tenting", "tenure", "termagant", "terminal", "terminate", "terminus", "terms", "terrapin", "terribly", "terrier", "terrify", "terse", "testament", "testator", "tested", "testify", "tethers", "teutonic", "thanks", "thaw", "thaws", "thearchy", "theater", "theirs", "theism", "then", "thence", "theocracy", "theocrasy", "theology", "theorist", "theorize", "therefor", "therefore", "thereupon", "thermal", "thesis", "they", "thicket", "thievery", "thigh", "thin", "think", "third", "thirsty", "thirtieth", "thomism", "thong", "thorn", "though", "thousand", "thrall", "thread", "threes", "threshed", "threw", "thrilled", "throb", "throes", "throne", "through", "throw", "thrums", "thrush", "thrusting", "thump", "thumped", "thusly", "thwarted", "thy", "thyroids", "tibias", "tic", "tick", "tickle", "tidying", "tight", "tightness", "tile", "tills", "tilted", "tilth", "timbre", "timeline", "timeout", "timorous", "tincture", "tinge", "tinkle", "tintype", "tipoff", "tipsy", "tirade", "tired", "tireless", "tiresome", "titled", "toddies", "toenail", "togas", "toggle", "toggling", "toil", "toilsome", "token", "tolerable", "tolerance", "tolerant", "tolerate", "tolling", "tomb", "tome", "tonic", "too", "tool", "toot", "toothless", "tootling", "top", "topics", "toponyms", "topping", "tops", "topsail", "torch", "tornados", "torpedo", "torpor", "torrid", "torso", "tortious", "tortuous", "tortured", "torturous", "toss", "tosser", "totaled", "touchy", "toughed", "toured", "touring", "touristy", "tousled", "towel", "towels", "towering", "townie", "toy", "toys", "trace", "traces", "trachoma", "tracking", "tractable", "trail", "trait", "trammel", "tramp", "tramped", "trance", "tranquil", "transact", "transcend", "transfer", "transfix", "transfuse", "transient", "translate", "transmit", "transmute", "transpire", "trashing", "trattoria", "travail", "traveling", "travesty", "trawl", "treachery", "treatise", "treble", "trebly", "tremor", "tremulous", "trenchant", "trendier", "trestle", "triad", "triads", "tribal", "tribe", "tribesman", "tribunal", "tribune", "trick", "trickery", "trickle", "tricolor", "tricycle", "trident", "triennial", "trim", "trimness", "trinity", "trio", "triple", "tripled", "tripod", "tripped", "trippy", "trisect", "trite", "triumvir", "trivial", "trombone", "trophies", "trots", "trotted", "troubling", "troupe", "trouper", "trove", "trowel", "truckload", "truculent", "truism", "trumpet", "truncated", "trundle", "trussed", "trusted", "trusting", "trusty", "truthful", "trying", "trypsin", "tuba", "tubing", "tuesdays", "tug", "tumble", "tunafish", "tune", "tuned", "tuneup", "tunisian", "turbans", "turbos", "turgid", "turkey", "turn", "turnabout", "turning", "turpitude", "tushes", "tussle", "tussles", "tutee", "tutelage", "tutelar", "tutorship", "tutsi", "twice", "twiddle", "twine", "twinge", "twirl", "twisting", "twitch", "twoyear", "tycoon", "typed", "typhus", "typical", "typified", "typify", "tyranny", "tyro", "ugh", "ugly", "ulterior", "ultimate", "ultimatum", "umbrage", "unable", "unanimity", "unanimous", "unbalance", "unbeaten", "unbelief", "unbiased", "unbind", "unblock", "unbounded", "unbridled", "uncannily", "uncanny", "uncared", "unclaimed", "uncle", "uncommon", "uncork", "uncrowded", "unction", "unctuous", "uncut", "undeceive", "underlie", "underling", "underman", "undermine", "underpaid", "underrate", "undersell", "undertake", "undertow", "undimmed", "undivided", "undo", "undone", "undue", "undulate", "undulous", "uneasy", "unfairly", "unfolding", "ungainly", "unglued", "ungodly", "unguent", "unharmed", "unhelpful", "unholy", "unhook", "unhooked", "unibody", "unify", "uninjured", "uninvited", "unique", "unison", "unisonant", "unite", "united", "univocal", "unkept", "unknown", "unlawful", "unlearned", "unlimited", "unnamed", "unnatural", "unnoticed", "unopposed", "unpeeled", "unplugged", "unpopular", "unproved", "unravel", "unripe", "unroofed", "unsay", "unseat", "unsettle", "unshod", "unsound", "unstuck", "untaxed", "untenable", "untended", "until", "untilled", "untimely", "untoward", "untreated", "unvaried", "unvarying", "unveil", "unveiled", "unwieldy", "unwise", "unyoke", "upbraid", "upcast", "updated", "upends", "upheaval", "upheave", "uplifted", "uploads", "upmarket", "upon", "upped", "upper", "uppermost", "upright", "uprising", "uproar", "uproot", "upsetting", "upstage", "upstart", "upstate", "uptick", "uptown", "upturn", "upwardly", "urban", "urbanity", "urchin", "urge", "urgency", "urgent", "usage", "used", "useful", "usual", "usurers", "usurious", "usurp", "usurped", "usury", "utensil", "utility", "utmost", "utter", "utterly", "vacate", "vaccinate", "vacillate", "vacuous", "vacuum", "vagabond", "vaginal", "vagrant", "vain", "vainglory", "valance", "vale", "valet", "valiant", "valid", "validate", "valleys", "valorous", "value", "vampire", "vane", "vanities", "vapid", "vaporizer", "variable", "variance", "variant", "variates", "variation", "variegate", "vassal", "vastly", "vastness", "vaulting", "veal", "vectors", "veered", "veering", "vegetal", "vegetate", "vehement", "veil", "velar", "veldt", "velocity", "velvety", "venal", "vendible", "vendition", "vendor", "veneer", "venerable", "venerate", "venereal", "vengeance", "vengeful", "venial", "venison", "venom", "venous", "ventilate", "veracious", "veracity", "veranda", "verbatim", "verbiage", "verbose", "verdant", "verify", "verily", "verity", "vermin", "vernal", "versatile", "version", "vertex", "vertical", "vertigo", "verve", "very", "vespers", "vessels", "vested", "vestige", "vestiges", "vesting", "vestment", "vet", "veto", "viability", "vibe", "vicarious", "viceroy", "victim", "videotape", "vie", "views", "vigilance", "vigilant", "vigilante", "vignette", "vilify", "vincible", "vindicate", "vinery", "vining", "viol", "viola", "violate", "violated", "violation", "violator", "viper", "virago", "virile", "virtu", "virtual", "virtue", "virtuoso", "virulence", "virulent", "visage", "viscera", "viscose", "viscount", "vision", "visit", "vista", "visual", "visualize", "vital", "vitality", "vitalize", "vitally", "vitiate", "vitiated", "vivacity", "vivify", "vocable", "vocal", "vocalist", "vocative", "vodka", "vogue", "voice", "voiceless", "void", "voiding", "voids", "volant", "volar", "volatile", "volition", "volitive", "voluble", "vomit", "vomited", "vomiting", "vomits", "voracious", "vortex", "votary", "voter", "votive", "vouchsafe", "vow", "voyager", "vulgarity", "wacko", "wad", "wadded", "wading", "wads", "wage", "waif", "waistcoat", "waistline", "waiter", "waiting", "waive", "wake", "waked", "walkable", "walker", "walkers", "walking", "walled", "wallop", "wallow", "waltz", "wampum", "wane", "waned", "wanes", "wangled", "wannabe", "wanted", "wanting", "ward", "warden", "warehouse", "warhead", "warheads", "warily", "wariness", "warlike", "warmed", "warning", "warp", "warpath", "warrior", "wary", "wash", "washtub", "waste", "wasteful", "watchful", "waterline", "wavelet", "waxy", "way", "weakens", "weakest", "weakling", "weal", "wean", "wear", "wearied", "wearisome", "webcams", "website", "wed", "wee", "weedy", "weekly", "weepers", "weigh", "weirdest", "weirdly", "welcome", "well", "welt", "west", "westward", "whatnot", "wheel", "wheeze", "whereupon", "wherever", "wherewith", "whet", "while", "whiled", "whimper", "whimsical", "whine", "whip", "whipping", "whippy", "whirr", "whirred", "whistle", "white", "whittle", "whiz", "whodunit", "wholly", "whomever", "whoop", "whoops", "whooshes", "wide", "widening", "widowed", "widower", "wield", "wielders", "wiggle", "wiggly", "wild", "wildcard", "wile", "wilful", "willowy", "wills", "wimp", "winced", "windage", "windswept", "wingman", "winsome", "winter", "wintry", "wipeout", "wiper", "wires", "wiretap", "wiry", "wise", "wisecrack", "wiseguy", "wishful", "withered", "within", "witless", "witling", "witticism", "wittiest", "wittingly", "wizen", "womanly", "wondered", "wonders", "wondrous", "woodland", "woodshed", "woolly", "woozy", "wordplay", "worklife", "workspace", "wormed", "worried", "worrying", "worthy", "would", "wouldst", "wound", "wrack", "wrangle", "wrap", "wrapped", "wreak", "wreath", "wrest", "wrestle", "wrestler", "wretch", "wriggle", "wrinkle", "writhe", "writing", "wrong", "wry", "yank", "yard", "yardage", "yards", "yarn", "yarns", "yaw", "yearend", "yearling", "yearning", "yeast", "yell", "yelling", "yellowing", "yells", "yen", "yielded", "yielding", "yin", "yogurts", "yoke", "yon", "you", "your", "yowled", "yowling", "yum", "zealot", "zeitgeist", "zenith", "zephyr", "zephyrs", "zero", "ziggurat", "zigs", "zings", "zionists", "zip", "zipped", "zodiac", "zooming" }; + +const size_t word_list_len = sizeof(word_list) / sizeof(const char*); diff --git a/apps/plugins/passmgr/wordlist.h b/apps/plugins/passmgr/wordlist.h new file mode 100644 index 0000000..8c59e89 --- /dev/null +++ b/apps/plugins/passmgr/wordlist.h @@ -0,0 +1,2 @@ +extern const char *word_list[]; +extern const size_t word_list_len; |