Fix voicing of incorrect run time (top time instead of run time). Simplify runtime...
[kugel-rb.git] / apps / plugins / keybox.c
blob389299d58077d334fc627cd4edba6422da66b5b4
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2008 Nils Wallménius
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
22 #include "md5.h"
23 PLUGIN_HEADER
25 #define KEYBOX_FILE PLUGIN_DIR "/apps/keybox.dat"
26 #define BLOCK_SIZE 8
27 #define MAX_ENTRIES 12*BLOCK_SIZE /* keep this a multiple of BLOCK_SIZE */
28 #define FIELD_LEN 32 /* should be enough for anyone ;) */
30 /* salt 4 bytes (needed for decryption) not encrypted padded with 4 bytes of zeroes
31 pwhash 16 bytes (to check for the right password) encrypted
32 encrypted data. */
34 #define HEADER_LEN 24
36 enum
38 FILE_OPEN_ERROR = -1
41 struct pw_entry
43 bool used;
44 char title[FIELD_LEN];
45 char name[FIELD_LEN];
46 char password[FIELD_LEN];
47 struct pw_entry *next;
50 struct pw_list
52 struct pw_entry first; /* always points to the first element in the list */
53 struct pw_entry entries[MAX_ENTRIES];
54 int num_entries;
55 } pw_list;
57 /* use this to access hashes in different ways, not byte order
58 independent but does it matter? */
59 union hash
61 uint8_t bytes[16];
62 uint32_t words[4];
65 static const struct plugin_api* rb;
66 MEM_FUNCTION_WRAPPERS(rb);
67 static char buffer[sizeof(struct pw_entry)*MAX_ENTRIES];
68 static int bytes_read = 0; /* bytes read into the buffer */
69 static struct gui_synclist kb_list;
70 static union hash key;
71 static char master_pw[FIELD_LEN];
72 static uint32_t salt;
73 static union hash pwhash;
74 static bool data_changed = false;
76 static int context_item_cb(int action, const struct menu_item_ex *this_item);
77 static void encrypt_buffer(char *buf, size_t size, uint32_t *key);
78 static void decrypt_buffer(char *buf, size_t size, uint32_t *key);
80 /* the following two functions are the reference TEA implementation by
81 David Wheeler and Roger Needham taken from
82 http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm */
84 static void encrypt(uint32_t* v, uint32_t* k)
86 uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
87 static const uint32_t delta=0x9e3779b9; /* a key schedule constant */
88 uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
89 for (i=0; i < 32; i++) { /* basic cycle start */
90 sum += delta;
91 v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
92 v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); /* end cycle */
94 v[0]=v0; v[1]=v1;
97 static void decrypt(uint32_t* v, uint32_t* k)
99 uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
100 static const uint32_t delta=0x9e3779b9; /* a key schedule constant */
101 uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
102 for (i=0; i<32; i++) { /* basic cycle start */
103 v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
104 v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
105 sum -= delta; /* end cycle */
107 v[0]=v0; v[1]=v1;
110 MENUITEM_RETURNVALUE(context_add_entry, "Add entry", 0,
111 NULL, Icon_NOICON);
112 MENUITEM_RETURNVALUE(context_edit_title, "Edit title", 1,
113 &context_item_cb, Icon_NOICON);
114 MENUITEM_RETURNVALUE(context_edit_name, "Edit user name", 2,
115 &context_item_cb, Icon_NOICON);
116 MENUITEM_RETURNVALUE(context_edit_password, "Edit password", 3,
117 &context_item_cb, Icon_NOICON);
118 MENUITEM_RETURNVALUE(context_delete_entry, "Delete entry", 4,
119 &context_item_cb, Icon_NOICON);
120 MENUITEM_RETURNVALUE(context_debug, "debug", 5,
121 &context_item_cb, Icon_NOICON);
123 MAKE_MENU(context_m, "Context menu",
124 context_item_cb, Icon_NOICON,
125 &context_add_entry, &context_edit_title, &context_edit_name,
126 &context_edit_password, &context_delete_entry);
128 static int context_item_cb(int action, const struct menu_item_ex *this_item)
130 if (action == ACTION_REQUEST_MENUITEM
131 && pw_list.num_entries == 0
132 && this_item != &context_add_entry)
134 return ACTION_EXIT_MENUITEM;
136 return action;
139 static char * kb_list_cb(int selected_item, void *data,
140 char *buffer, size_t buffer_len)
142 (void)data;
143 int i;
144 struct pw_entry *entry = pw_list.first.next;
145 for (i = 0; i < selected_item; i++)
147 if (entry)
148 entry = entry->next;
150 if (!entry)
151 return NULL;
153 rb->snprintf(buffer, buffer_len, "%s", entry->title);
155 return buffer;
158 static void init_ll(void)
160 pw_list.first.next = &pw_list.entries[0];
161 pw_list.entries[0].next = NULL;
162 pw_list.num_entries = 0;
165 static void delete_entry(int selected_item)
167 int i;
168 struct pw_entry *entry = &pw_list.first;
169 struct pw_entry *entry2;
171 /* find the entry before the one to delete */
172 for (i = 0; i < selected_item; i++)
174 if (entry->next)
175 entry = entry->next;
177 entry2 = entry->next;
178 if (!entry2)
179 return;
181 entry->next = entry2->next;
183 entry2->used = false;
184 entry2->name[0] = '\0';
185 entry2->password[0] = '\0';
186 entry2->next = NULL;
188 rb->gui_synclist_set_nb_items(&kb_list, --pw_list.num_entries);
189 data_changed = true;
192 static void add_entry(int selected_item)
194 int i, j;
195 struct pw_entry *entry = pw_list.first.next;
196 for (i = 0; i < MAX_ENTRIES && pw_list.entries[i].used; i++)
199 if (pw_list.entries[i].used)
201 rb->splash(HZ, "Password list full");
202 return;
205 rb->splash(HZ, "Enter title");
206 pw_list.entries[i].title[0] = '\0';
207 rb->kbd_input(pw_list.entries[i].title, FIELD_LEN);
208 rb->splash(HZ, "Enter name");
209 pw_list.entries[i].name[0] = '\0';
210 rb->kbd_input(pw_list.entries[i].name, FIELD_LEN);
211 rb->splash(HZ, "Enter password");
212 pw_list.entries[i].password[0] = '\0';
213 rb->kbd_input(pw_list.entries[i].password, FIELD_LEN);
215 for (j = 0; j < selected_item; j++)
217 if (entry->next)
218 entry = entry->next;
221 rb->gui_synclist_set_nb_items(&kb_list, ++pw_list.num_entries);
223 pw_list.entries[i].used = true;
224 pw_list.entries[i].next = entry->next;
226 entry->next = &pw_list.entries[i];
228 if (entry->next == entry)
229 entry->next = NULL;
231 data_changed = true;
234 static void edit_title(int selected_item)
236 int i;
237 struct pw_entry *entry = pw_list.first.next;
238 for (i = 0; i < selected_item; i++)
240 if (entry->next)
241 entry = entry->next;
243 if (rb->kbd_input(entry->title, FIELD_LEN) == 0)
244 data_changed = true;
247 static void edit_name(int selected_item)
249 int i;
250 struct pw_entry *entry = pw_list.first.next;
251 for (i = 0; i < selected_item; i++)
253 if (entry->next)
254 entry = entry->next;
256 if (rb->kbd_input(entry->name, FIELD_LEN) == 0)
257 data_changed = true;
260 static void edit_pw(int selected_item)
262 int i;
263 struct pw_entry *entry = pw_list.first.next;
264 for (i = 0; i < selected_item; i++)
266 if (entry->next)
267 entry = entry->next;
269 if (rb->kbd_input(entry->password, FIELD_LEN) == 0)
270 data_changed = true;
273 static void context_menu(int selected_item)
275 int selection, result;
276 bool exit = false;
278 do {
279 result = rb->do_menu(&context_m, &selection, NULL, false);
280 switch (result) {
281 case 0:
282 add_entry(selected_item);
283 return;
284 case 1:
285 edit_title(selected_item);
286 return;
287 case 2:
288 edit_name(selected_item);
289 return;
290 case 3:
291 edit_pw(selected_item);
292 return;
293 case 4:
294 delete_entry(selected_item);
295 return;
296 default:
297 exit = true;
298 break;
300 rb->yield();
301 } while (!exit);
304 static void splash_pw(int selected_item)
306 int i;
307 struct pw_entry *entry = pw_list.first.next;
309 for (i = 0; i < selected_item; i++)
311 if (entry->next)
312 entry = entry->next;
314 if (entry->name != '\0')
315 rb->splash(0, "%s %s", entry->name, entry->password);
316 else
317 rb->splash(0, "%s", entry->password);
318 rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
321 static void hash_pw(union hash *out)
323 int i;
324 struct md5_s pw_md5;
326 InitMD5(&pw_md5);
327 AddMD5(&pw_md5, master_pw, rb->strlen(master_pw));
328 EndMD5(&pw_md5);
330 for (i = 0; i < 4; i++)
331 out->words[i] = htole32(pw_md5.p_digest[i]);
334 static void make_key(void)
336 int i;
337 char buf[sizeof(master_pw) + sizeof(salt) + 1];
338 struct md5_s key_md5;
339 size_t len = rb->strlen(master_pw);
341 rb->strncpy(buf, master_pw, sizeof(buf));
343 rb->memcpy(&buf[len], &salt, sizeof(salt));
345 InitMD5(&key_md5);
346 AddMD5(&key_md5, buf, rb->strlen(buf));
347 EndMD5(&key_md5);
349 for (i = 0; i < 4; i++)
350 key.words[i] = key_md5.p_digest[i];
353 static void decrypt_buffer(char *buf, size_t size, uint32_t *key)
355 unsigned int i;
356 uint32_t block[2];
358 for (i = 0; i < size/BLOCK_SIZE; i++)
360 rb->memcpy(&block[0], &buf[i*BLOCK_SIZE], sizeof(block));
362 block[0] = letoh32(block[0]);
363 block[1] = letoh32(block[1]);
365 decrypt(&block[0], key);
367 /* byte swap one block */
368 block[0] = letoh32(block[0]);
369 block[1] = letoh32(block[1]);
371 rb->memcpy(&buf[i*BLOCK_SIZE], &block[0], sizeof(block));
375 static void encrypt_buffer(char *buf, size_t size, uint32_t *key)
377 unsigned int i;
378 uint32_t block[2];
380 for (i = 0; i < size/BLOCK_SIZE; i++)
382 rb->memcpy(&block[0], &buf[i*BLOCK_SIZE], sizeof(block));
384 /* byte swap one block */
385 block[0] = htole32(block[0]);
386 block[1] = htole32(block[1]);
388 encrypt(&block[0], key);
390 block[0] = htole32(block[0]);
391 block[1] = htole32(block[1]);
393 rb->memcpy(&buf[i*BLOCK_SIZE], &block[0], sizeof(block));
397 static int parse_buffer(void)
399 int i;
400 int len;
401 struct pw_entry *entry = pw_list.first.next;
402 char *start, *end;
403 start = &buffer[HEADER_LEN];
405 rb->memcpy(&salt, &buffer[0], sizeof(salt));
406 make_key();
408 decrypt_buffer(&buffer[8], bytes_read - 8, &key.words[0]);
410 if (rb->memcmp(&buffer[8], &pwhash, sizeof(union hash)))
412 rb->splash(HZ*2, "Wrong password");
413 return -1;
416 for (i = 0; i < MAX_ENTRIES; i++)
418 end = rb->strchr(start, '\0'); /* find eol */
419 len = end - &buffer[HEADER_LEN];
420 if ((len > bytes_read + HEADER_LEN) || start == end)
422 break;
425 rb->strncpy(entry->title, start, FIELD_LEN);
426 start = end + 1;
428 end = rb->strchr(start, '\0'); /* find eol */
429 len = end - &buffer[HEADER_LEN];
430 if (len > bytes_read + HEADER_LEN)
432 break;
435 rb->strncpy(entry->name, start, FIELD_LEN);
436 start = end + 1;
438 end = rb->strchr(start, '\0'); /* find eol */
439 len = end - &buffer[HEADER_LEN];
440 if (len > bytes_read + HEADER_LEN)
442 break;
444 rb->strncpy(entry->password, start, FIELD_LEN);
445 start = end + 1;
446 entry->used = true;
447 if (i + 1 < MAX_ENTRIES - 1)
449 entry->next = &pw_list.entries[i+1];
450 entry = entry->next;
452 else
454 break;
457 entry->next = NULL;
458 pw_list.num_entries = i;
459 rb->gui_synclist_set_nb_items(&kb_list, pw_list.num_entries);
460 return 0;
463 static void write_output(int fd)
465 size_t bytes_written;
466 int i;
467 size_t len, size;
468 char *p = &buffer[HEADER_LEN]; /* reserve space for salt + hash */
470 rb->memcpy(&buffer[8], &pwhash, sizeof(union hash));
471 struct pw_entry *entry = pw_list.first.next;
473 for (i = 0; i < pw_list.num_entries; i++)
475 len = rb->strlen(entry->title);
476 rb->strncpy(p, entry->title, len+1);
477 p += len+1;
478 len = rb->strlen(entry->name);
479 rb->strncpy(p, entry->name, len+1);
480 p += len+1;
481 len = rb->strlen(entry->password);
482 rb->strncpy(p, entry->password, len+1);
483 p += len+1;
484 if (entry->next)
485 entry = entry->next;
487 *p++ = '\0'; /* mark the end of the list */
489 /* round up to a number divisible by BLOCK_SIZE */
490 size = ((p - buffer + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE;
492 salt = rb->rand();
493 make_key();
495 encrypt_buffer(&buffer[8], size, &key.words[0]);
496 rb->memcpy(&buffer[0], &salt, sizeof(salt));
498 bytes_written = rb->write(fd, &buffer, size);
501 static int enter_pw(char *pw_buf, size_t buflen, bool new_pw)
503 char buf[2][sizeof(master_pw)];
504 rb->memset(buf, 0, sizeof(buf));
505 rb->memset(master_pw, 0, sizeof(master_pw));
507 if (new_pw)
509 rb->splash(HZ, "Enter new master password");
510 rb->kbd_input(buf[0], sizeof(buf[0]));
511 rb->splash(HZ, "Confirm master password");
512 rb->kbd_input(buf[1], sizeof(buf[1]));
514 if (rb->strcmp(buf[0], buf[1]))
516 rb->splash(HZ, "Password mismatch");
517 return -1;
519 else
521 rb->strncpy(pw_buf, buf[0], buflen);
522 hash_pw(&pwhash);
523 return 0;
527 rb->splash(HZ, "Enter master password");
528 if (rb->kbd_input(pw_buf, buflen))
529 return -1;
530 hash_pw(&pwhash);
531 return 0;
534 static int keybox(void)
536 int button, fd;
537 bool new_file = !rb->file_exists(KEYBOX_FILE);
538 bool done = false;
540 if (enter_pw(master_pw, sizeof (master_pw), new_file))
541 return 0;
543 /* Read the existing file */
544 if (!new_file)
546 fd = rb->open(KEYBOX_FILE, O_RDONLY);
547 if (fd < 0)
548 return FILE_OPEN_ERROR;
549 bytes_read = rb->read(fd, &buffer, sizeof(buffer));
551 if (parse_buffer())
552 return 0;
554 rb->close(fd);
557 while (!done)
559 rb->gui_syncstatusbar_draw(rb->statusbars, true);
560 rb->gui_synclist_draw(&kb_list);
561 button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
562 if (rb->gui_synclist_do_button(&kb_list, &button, LIST_WRAP_ON))
563 continue;
565 switch (button)
567 case ACTION_STD_OK:
568 splash_pw(rb->gui_synclist_get_sel_pos(&kb_list));
569 break;
570 case ACTION_STD_CONTEXT:
571 context_menu(rb->gui_synclist_get_sel_pos(&kb_list));
572 break;
573 case ACTION_STD_CANCEL:
574 done = true;
575 break;
577 rb->yield();
580 if (data_changed)
582 fd = rb->open(KEYBOX_FILE, O_WRONLY | O_CREAT | O_TRUNC);
583 if (fd < 0)
584 return FILE_OPEN_ERROR;
585 write_output(fd);
586 rb->close(fd);
589 return 0;
592 static void reset(void)
594 static const char *message_lines[]=
595 {"Do you really want", "to reset keybox?"};
596 static const char *yes_lines[]=
597 {"Keybox reset."};
598 static const struct text_message message={message_lines, 2};
599 static const struct text_message yes_message={yes_lines, 1};
601 if(rb->gui_syncyesno_run(&message, &yes_message, NULL) == YESNO_YES)
603 rb->remove(KEYBOX_FILE);
604 rb->memset(&buffer, 0, sizeof(buffer));
605 rb->memset(&pw_list, 0, sizeof(pw_list));
606 init_ll();
610 static int main_menu(void)
612 int selection, result, ret;
613 bool exit = false;
615 MENUITEM_STRINGLIST(menu,"Keybox", NULL, "Enter Keybox",
616 "Reset Keybox", "Exit");
618 do {
619 result = rb->do_menu(&menu, &selection, NULL, false);
620 switch (result) {
621 case 0:
622 ret = keybox();
623 if (ret)
624 return ret;
625 break;
626 case 1:
627 reset();
628 break;
629 case 2:
630 exit = true;
631 break;
633 rb->yield();
634 } while (!exit);
636 return 0;
639 enum plugin_status plugin_start(const struct plugin_api *api,
640 const void *parameter)
642 (void)parameter;
643 rb = api;
644 int ret;
646 rb->gui_synclist_init(&kb_list, &kb_list_cb, NULL, false, 1, NULL);
648 rb->gui_synclist_set_title(&kb_list, "Keybox", NOICON);
649 rb->gui_synclist_set_icon_callback(&kb_list, NULL);
650 rb->gui_synclist_set_nb_items(&kb_list, 0);
651 rb->gui_synclist_limit_scroll(&kb_list, false);
652 rb->gui_synclist_select_item(&kb_list, 0);
654 md5_init(api);
656 init_ll();
657 ret = main_menu();
659 switch (ret)
661 case FILE_OPEN_ERROR:
662 rb->splash(HZ*2, "Error opening file");
663 return PLUGIN_ERROR;
666 return PLUGIN_OK;