cb94b3ae2e
rb core allows you to load custom keyboard layouts this patch adds the ability to load a keyboard layout in a buffer the custom layout is temporary and does not overwrite the current layout use like so: unsigned short kbd[64]; unsigned short *kbd_p = kbd; if (!kbd_create_layout("ABCD1234\n", kbd, sizeof(kbd))) kbd_p = NULL; rb->kbd_input(buf,sizeof(buf), kbd_p); Change-Id: I7be2bd4a1b4797a147fa70228a9749dc56ac052a
1094 lines
30 KiB
C
1094 lines
30 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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.");
|
|
char* buf = accounts[next_slot].name;
|
|
if(rb->kbd_input(buf, sizeof(accounts[next_slot].name), NULL) < 0)
|
|
return;
|
|
|
|
if(acct_exists(buf))
|
|
{
|
|
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), NULL) < 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), NULL) < 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), NULL) < 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)
|
|
{
|
|
if(!accounts[acct].is_totp)
|
|
{
|
|
rb->splashf(0, "%0*d", accounts[acct].digits,
|
|
HOTP(accounts[acct].secret,
|
|
accounts[acct].sec_len,
|
|
accounts[acct].hotp_counter,
|
|
accounts[acct].digits));
|
|
++accounts[acct].hotp_counter;
|
|
}
|
|
#if CONFIG_RTC
|
|
else
|
|
{
|
|
rb->splashf(0, "%0*d (%ld seconds(s) left)", accounts[acct].digits,
|
|
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), NULL) < 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), NULL) < 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), NULL) < 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), NULL) < 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;
|
|
}
|