diff options
| author | Franklin Wei <frankhwei536@gmail.com> | 2016-05-25 21:43:32 -0400 |
|---|---|---|
| committer | Franklin Wei <frankhwei536@gmail.com> | 2016-06-05 14:25:09 -0400 |
| commit | 30d7ead6af5c28ff72d6e47bab7e044657be7ce6 (patch) | |
| tree | 57b38d872bb37cf361fb2777ba5da48d5fc14a3c /apps/plugins/otp.c | |
| parent | 59ae562a3280105595e690ebff772ea4f7790970 (diff) | |
| download | rockbox-30d7ead6af5c28ff72d6e47bab7e044657be7ce6.zip rockbox-30d7ead6af5c28ff72d6e47bab7e044657be7ce6.tar.gz rockbox-30d7ead6af5c28ff72d6e47bab7e044657be7ce6.tar.bz2 rockbox-30d7ead6af5c28ff72d6e47bab7e044657be7ce6.tar.xz | |
One-Time Password client (HOTP and TOTP)
* Implements RFC 4226 (HOTP) and RFC 6238 (TOTP)
* Adds sha1.c to apps/plugins/lib (orignally tools/hmac-sha1.c)
* See manual entry for more information
Change-Id: Ia4a4031b29f97361b541e71438aa7f3ea82212f2
Diffstat (limited to 'apps/plugins/otp.c')
| -rw-r--r-- | apps/plugins/otp.c | 1095 |
1 files changed, 1095 insertions, 0 deletions
diff --git a/apps/plugins/otp.c b/apps/plugins/otp.c new file mode 100644 index 0000000..69cb8b7 --- /dev/null +++ b/apps/plugins/otp.c @@ -0,0 +1,1095 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + * + ****************************************************************************/ + +/* simple OTP plugin */ + +/* see RFCs 4226, 6238 for more information about the algorithms used */ + +#include "plugin.h" + +#include "lib/display_text.h" +#include "lib/pluginlib_actions.h" +#include "lib/pluginlib_exit.h" +#include "lib/sha1.h" + +#define MAX_NAME 50 +#define SECRET_MAX 256 +#define URI_MAX 256 +#define ACCT_FILE PLUGIN_APPS_DATA_DIR "/otp.dat" + +#define MAX(a, b) (((a)>(b))?(a):(b)) + +struct account_t { + char name[MAX_NAME]; + + bool is_totp; // hotp otherwise + + union { + uint64_t hotp_counter; + int totp_period; + }; + + int digits; + + unsigned char secret[SECRET_MAX]; + int sec_len; +}; + +static int max_accts = 0; + +/* in plugin buffer */ +static struct account_t *accounts = NULL; + +static int next_slot = 0; + +/* in SECONDS, asked for on first run */ +static int time_offs = 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; +} + +#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) +{ + 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); +} + +static bool read_accts(void) +{ + int fd = rb->open(ACCT_FILE, O_RDONLY); + if(fd < 0) + return false; + + char buf[4]; + char magic[4] = { 'O', 'T', 'P', '1' }; + rb->read(fd, buf, 4); + if(memcmp(magic, buf, 4)) + { + rb->splash(HZ * 2, "Corrupt save data!"); + rb->close(fd); + return false; + } + + rb->read(fd, &time_offs, sizeof(time_offs)); + + 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 void save_accts(void) +{ + int fd = rb->open(ACCT_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0600); + rb->fdprintf(fd, "OTP1"); + + rb->write(fd, &time_offs, sizeof(time_offs)); + + for(int i = 0; i < next_slot; ++i) + rb->write(fd, accounts + i, sizeof(struct account_t)); + rb->close(fd); +} + +static void add_acct_file(void) +{ + char fname[MAX_PATH]; + rb->splash(HZ * 2, "Please choose file containing URI(s)."); + int before = next_slot; + if(browse(fname, sizeof(fname), "/")) + { + int fd = rb->open(fname, O_RDONLY); + do { + memset(accounts + next_slot, 0, sizeof(struct account_t)); + + accounts[next_slot].digits = 6; + + char uri_buf[URI_MAX]; + if(!rb->read_line(fd, uri_buf, sizeof(uri_buf))) + break; + + if(next_slot >= max_accts) + { + rb->splash(HZ * 2, "Account limit reached: some accounts not added."); + break; + } + + /* check for URI prefix */ + if(rb->strncmp(uri_buf, "otpauth://", 10)) + continue; + + char *save; + char *tok = rb->strtok_r(uri_buf + 10, "/", &save); + if(!rb->strcmp(tok, "totp")) + { + accounts[next_slot].is_totp = true; + accounts[next_slot].totp_period = 30; +#if !CONFIG_RTC + rb->splash(2 * HZ, "TOTP not supported!"); + continue; +#endif + } + else if(!rb->strcmp(tok, "hotp")) + { + accounts[next_slot].is_totp = false; + 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].is_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].is_totp) + { + 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; + } + + ++next_slot; + + 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); + save_accts(); + } +} + +static void add_acct_manual(void) +{ + if(next_slot >= max_accts) + { + rb->splashf(HZ * 2, "Account limit reached!"); + return; + } + 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; + } + + rb->splash(HZ * 2, "Enter base32-encoded secret."); + + char temp_buf[SECRET_MAX * 2]; + memset(temp_buf, 0, sizeof(temp_buf)); + + if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0) + return; + + 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; + } + +#if CONFIG_RTC + const struct text_message prompt = { (const char*[]) {"Is this a TOTP account?", "The protocol can be determined from the URI."}, 2}; + enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); + if(response == YESNO_NO) + accounts[next_slot].is_totp = false; + else + accounts[next_slot].is_totp = true; +#endif + + memset(temp_buf, 0, sizeof(temp_buf)); + + if(!accounts[next_slot].is_totp) + { + rb->splash(HZ * 2, "Enter counter (0 is normal)."); + temp_buf[0] = '0'; + } + else + { + rb->splash(HZ * 2, "Enter time step (30 is normal)."); + temp_buf[0] = '3'; + temp_buf[1] = '0'; + } + + if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0) + return; + + if(!accounts[next_slot].is_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 normal)."); + + 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; + } + + ++next_slot; + + save_accts(); + + rb->splashf(HZ * 2, "Success."); +} + +static void add_acct(void) +{ + MENUITEM_STRINGLIST(menu, "Add Account", NULL, + "From URI on disk", + "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; + } + } +} + +static void show_code(int acct) +{ + /* rockbox's printf doesn't support a variable field width afaik */ + char format_buf[64]; + if(!accounts[acct].is_totp) + { + rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd", accounts[acct].digits); + rb->splashf(0, format_buf, HOTP(accounts[acct].secret, + accounts[acct].sec_len, + accounts[acct].hotp_counter, + accounts[acct].digits)); + ++accounts[acct].hotp_counter; + } +#if CONFIG_RTC + else + { + rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd (%%ld second(s) left)", accounts[acct].digits); + rb->splashf(0, format_buf, TOTP(accounts[acct].secret, + accounts[acct].sec_len, + accounts[acct].totp_period, + accounts[acct].digits), + accounts[acct].totp_period - get_utc() % accounts[acct].totp_period); + } +#else + else + { + rb->splash(0, "TOTP not supported on this device!"); + } +#endif + rb->sleep(HZ * 2); + while(1) + { + int button = rb->button_get(true); + if(button && !(button & BUTTON_REL)) + break; + rb->yield(); + } + + save_accts(); + rb->lcd_clear_display(); +} + +static void gen_codes(void) +{ + 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_putsf(0, 0, "Generate Code"); + 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: + show_code(idx); + break; + case PLA_CANCEL: + case PLA_EXIT: + exit_on_usb(button); + return; + default: + break; + } + rb->lcd_clear_display(); + rb->lcd_putsf(0, 0, "Generate Code"); + rb->lcd_putsf(0, 1, "%s", accounts[idx].name); + rb->lcd_update(); + } +} + +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; + } +} + +char data_buf[MAX(MAX_NAME, SECRET_MAX * 2)]; +char temp_sec[SECRET_MAX]; +size_t old_len; + +static void edit_menu(int acct) +{ + rb->splashf(HZ, "Editing account `%s'.", accounts[acct].name); + + /* HACK ALERT */ + /* two different menus, one handling logic */ + MENUITEM_STRINGLIST(menu_1, "Edit Account", NULL, + "Rename", + "Delete", + "Change HOTP Counter", + "Change Digit Count", + "Change Shared Secret", + "Back"); + + MENUITEM_STRINGLIST(menu_2, "Edit Account", NULL, + "Rename", // 0 + "Delete", // 1 + "Change TOTP Period", // 2 + "Change Digit Count", // 3 + "Change Shared Secret", // 4 + "Back"); // 5 + + const struct menu_item_ex *menu = (accounts[acct].is_totp) ? &menu_2 : &menu_1; + + bool quit = false; + int sel = 0; + 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)); + save_accts(); + break; + case 1: // delete + if(danger_confirm()) + { + rb->memmove(accounts + acct, accounts + acct + 1, (next_slot - acct - 1) * sizeof(struct account_t)); + --next_slot; + save_accts(); + rb->splashf(HZ, "Deleted."); + return; + } + else + rb->splash(HZ, "Not confirmed."); + break; + case 2: // HOTP counter OR TOTP period + if(accounts[acct].is_totp) + rb->snprintf(data_buf, sizeof(data_buf), "%d", (int)accounts[acct].hotp_counter); + else + rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].totp_period); + + if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0) + break; + + if(accounts[acct].is_totp) + accounts[acct].totp_period = rb->atoi(data_buf); + else + accounts[acct].hotp_counter = rb->atoi(data_buf); + + save_accts(); + + rb->splash(HZ, "Success."); + break; + case 3: // digits + 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_accts(); + + rb->splash(HZ, "Success."); + break; + case 4: // secret + old_len = accounts[acct].sec_len; + memcpy(temp_sec, accounts[acct].secret, accounts[acct].sec_len); + 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) + { + 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_accts(); + rb->splash(HZ, "Success."); + break; + case 5: + quit = true; + break; + default: + break; + } + } +} + +static void edit_accts(void) +{ + 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_putsf(0, 0, "Edit Account"); + 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: + edit_menu(idx); + if(!next_slot) + return; + if(idx == next_slot) + idx = 0; + break; + case PLA_CANCEL: + case PLA_EXIT: + return; + default: + exit_on_usb(button); + break; + } + rb->lcd_clear_display(); + rb->lcd_putsf(0, 0, "Edit Account"); + rb->lcd_putsf(0, 1, "%s", accounts[idx].name); + rb->lcd_update(); + } +} + +#if CONFIG_RTC + +/* label is like this: [+/-]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 Offset", 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 = 0; + for(unsigned int i = 0; i < ARRAYLEN(menu_); ++i) + if(time_offs == get_time_seconds(menu_[i])) + { + sel = i; + break; + } + + /* relies on menu internals */ + rb->do_menu(&menu, &sel, NULL, false); + + /* see apps/menu.h */ + const char *label = menu_[sel]; + + return get_time_seconds(label); + +#if 0 + /* provided in case menu internals change */ + 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 + +static void adv_menu(void) +{ + MENUITEM_STRINGLIST(menu, "Advanced", NULL, + "Edit Account", + "Delete ALL accounts", +#if CONFIG_RTC + "Change Time Offset", +#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: + if(danger_confirm()) + { + next_slot = 0; + save_accts(); + rb->splash(HZ, "It is done, my master."); + } + else + rb->splash(HZ, "Not confirmed."); + break; +#if CONFIG_RTC + case 2: + time_offs = get_time_offs(); + break; + case 3: +#else + case 2: +#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[] = { "One-Time Password Manager", "", + "Introduction", "", + "This", "plugin", "allows", "you", "to", "generate", "one-time", "passwords", "to", "provide", "a", "second", "factor", "of", "authentication", "for", "services", "that", "support", "it.", + "It", "suppports", "both", "event-based", "(HOTP),", "and", "time-based", "(TOTP)", "password", "schemes.", + "In", "order", "to", "ensure", "proper", "functioning", "of", "time-based", "passwords", "ensure", "that", "the", "clock", "is", "accurate", "to", "within", "30", "seconds", "of", "actual", "time." + "Note", "that", "some", "devices", "lack", "a", "real-time", "clock,", "so", "time-based", "passwords", "are", "not", "supported", "on", "those", "targets." }; + + struct style_text style[] = { + {0, TEXT_CENTER | TEXT_UNDERLINE}, + {2, C_RED}, + LAST_STYLE_ITEM + }; + + display_text(ARRAYLEN(help_text), help_text, style, NULL, true); +} + +/* this is the plugin entry point */ +enum plugin_status plugin_start(const void* parameter) +{ + (void)parameter; + + /* self-test with RFC 4226 values */ + if(HOTP("12345678901234567890", rb->strlen("12345678901234567890"), 1, 6) != 287082) + { + return PLUGIN_ERROR; + } + + size_t bufsz; + accounts = rb->plugin_get_buffer(&bufsz); + max_accts = bufsz / sizeof(struct account_t); + + if(!read_accts()) +#if CONFIG_RTC + { + time_offs = get_time_offs(); + } +#else + { + ; + } +#endif + + MENUITEM_STRINGLIST(menu, "One-Time Password Manager", NULL, + "Add Account", + "Generate Code", + "Help", + "Advanced", + "Quit"); + + bool quit = false; + int sel = 0; + while(!quit) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + add_acct(); + break; + case 1: + gen_codes(); + break; + case 2: + show_help(); + break; + case 3: + adv_menu(); + break; + case 4: + quit = 1; + break; + default: + break; + } + } + + /* save to disk */ + save_accts(); + + /* tell Rockbox that we have completed successfully */ + return PLUGIN_OK; +} |