Fix r29054 red...yeah?
[kugel-rb.git] / apps / plugins / keybox.c
blobaf41b3fdd8d2e530d7016dcf38a3f28357dce94a
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 "lib/playback_control.h"
23 #include "lib/md5.h"
26 #define KEYBOX_FILE PLUGIN_APPS_DIR "/keybox.dat"
27 #define BLOCK_SIZE 8
28 #define MAX_ENTRIES 12*BLOCK_SIZE /* keep this a multiple of BLOCK_SIZE */
29 #define FIELD_LEN 32 /* should be enough for anyone ;) */
31 /* The header begins with the unencrypted salt (4 bytes) padded with 4 bytes of
32 zeroes. After that comes the encrypted hash of the master password (16 bytes) */
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 char buffer[sizeof(struct pw_entry)*MAX_ENTRIES];
66 static int bytes_read = 0; /* bytes read into the buffer */
67 static struct gui_synclist kb_list;
68 static union hash key;
69 static char master_pw[FIELD_LEN];
70 static uint32_t salt;
71 static union hash pwhash;
72 static bool data_changed = false;
74 static void encrypt_buffer(char *buf, size_t size, uint32_t *key);
75 static void decrypt_buffer(char *buf, size_t size, uint32_t *key);
77 /* the following two functions are the reference TEA implementation by
78 David Wheeler and Roger Needham taken from
79 http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm */
81 static void encrypt(uint32_t* v, uint32_t* k)
83 uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
84 static const uint32_t delta=0x9e3779b9; /* a key schedule constant */
85 uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
86 for (i=0; i < 32; i++) { /* basic cycle start */
87 sum += delta;
88 v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
89 v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); /* end cycle */
91 v[0]=v0; v[1]=v1;
94 static void decrypt(uint32_t* v, uint32_t* k)
96 uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
97 static const uint32_t delta=0x9e3779b9; /* a key schedule constant */
98 uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
99 for (i=0; i<32; i++) { /* basic cycle start */
100 v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
101 v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
102 sum -= delta; /* end cycle */
104 v[0]=v0; v[1]=v1;
107 static int context_item_cb(int action, const struct menu_item_ex *this_item)
109 int i = (intptr_t)this_item;
110 if (action == ACTION_REQUEST_MENUITEM
111 && pw_list.num_entries == 0
112 && (i != 0 && i != 5))
114 return ACTION_EXIT_MENUITEM;
116 return action;
119 MENUITEM_STRINGLIST(context_m, "Context menu", context_item_cb,
120 "Add entry",
121 "Edit title", "Edit user name", "Edit password",
122 "Delete entry",
123 "Playback Control");
125 static const char* kb_list_cb(int selected_item, void *data,
126 char *buffer, size_t buffer_len)
128 (void)data;
129 int i;
130 struct pw_entry *entry = pw_list.first.next;
131 for (i = 0; i < selected_item; i++)
133 if (entry)
134 entry = entry->next;
136 if (!entry)
137 return NULL;
139 rb->snprintf(buffer, buffer_len, "%s", entry->title);
141 return buffer;
144 static void init_ll(void)
146 pw_list.first.next = &pw_list.entries[0];
147 pw_list.entries[0].next = NULL;
148 pw_list.num_entries = 0;
151 static void delete_entry(int selected_item)
153 int i;
154 struct pw_entry *entry = &pw_list.first;
155 struct pw_entry *entry2;
157 /* find the entry before the one to delete */
158 for (i = 0; i < selected_item; i++)
160 if (entry->next)
161 entry = entry->next;
163 entry2 = entry->next;
164 if (!entry2)
165 return;
167 entry->next = entry2->next;
169 entry2->used = false;
170 entry2->name[0] = '\0';
171 entry2->password[0] = '\0';
172 entry2->next = NULL;
174 rb->gui_synclist_set_nb_items(&kb_list, --pw_list.num_entries);
175 if(!pw_list.num_entries)
176 init_ll();
177 data_changed = true;
180 static void add_entry(int selected_item)
182 int i, j;
183 struct pw_entry *entry = pw_list.first.next;
184 for (i = 0; i < MAX_ENTRIES && pw_list.entries[i].used; i++)
187 if (MAX_ENTRIES == i)
189 rb->splash(HZ, "Password list full");
190 return;
193 rb->splash(HZ, "Enter title");
194 pw_list.entries[i].title[0] = '\0';
195 if (rb->kbd_input(pw_list.entries[i].title, FIELD_LEN) < 0)
196 return;
198 rb->splash(HZ, "Enter name");
199 pw_list.entries[i].name[0] = '\0';
200 if (rb->kbd_input(pw_list.entries[i].name, FIELD_LEN) < 0)
202 pw_list.entries[i].title[0] = '\0';
203 return;
206 rb->splash(HZ, "Enter password");
207 pw_list.entries[i].password[0] = '\0';
208 if (rb->kbd_input(pw_list.entries[i].password, FIELD_LEN) < 0)
210 pw_list.entries[i].title[0] = '\0';
211 pw_list.entries[i].name[0] = '\0';
212 return;
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 = 0, 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 case 5:
297 playback_control(NULL);
298 return;
299 default:
300 exit = true;
301 break;
303 rb->yield();
304 } while (!exit);
307 static void splash_pw(int selected_item)
309 int i;
310 struct pw_entry *entry = pw_list.first.next;
312 for (i = 0; i < selected_item; i++)
314 if (entry->next)
315 entry = entry->next;
317 if (entry->name != '\0')
318 rb->splashf(0, "%s %s", entry->name, entry->password);
319 else
320 rb->splashf(0, "%s", entry->password);
321 rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
324 static void hash_pw(union hash *out)
326 int i;
327 struct md5_s pw_md5;
329 InitMD5(&pw_md5);
330 AddMD5(&pw_md5, master_pw, rb->strlen(master_pw));
331 EndMD5(&pw_md5);
333 for (i = 0; i < 4; i++)
334 out->words[i] = htole32(pw_md5.p_digest[i]);
337 static void make_key(void)
339 int i;
340 char buf[sizeof(master_pw) + sizeof(salt) + 1] = {0};
341 struct md5_s key_md5;
342 size_t len = rb->strlen(master_pw);
344 rb->strlcpy(buf, master_pw, sizeof(buf));
346 rb->memcpy(&buf[len], &salt, sizeof(salt));
348 InitMD5(&key_md5);
349 AddMD5(&key_md5, buf, rb->strlen(buf));
350 EndMD5(&key_md5);
352 for (i = 0; i < 4; i++)
353 key.words[i] = key_md5.p_digest[i];
356 static void decrypt_buffer(char *buf, size_t size, uint32_t *key)
358 unsigned int i;
359 uint32_t block[2];
361 for (i = 0; i < size/BLOCK_SIZE; i++)
363 rb->memcpy(&block[0], &buf[i*BLOCK_SIZE], sizeof(block));
365 block[0] = letoh32(block[0]);
366 block[1] = letoh32(block[1]);
368 decrypt(&block[0], key);
370 /* byte swap one block */
371 block[0] = letoh32(block[0]);
372 block[1] = letoh32(block[1]);
374 rb->memcpy(&buf[i*BLOCK_SIZE], &block[0], sizeof(block));
378 static void encrypt_buffer(char *buf, size_t size, uint32_t *key)
380 unsigned int i;
381 uint32_t block[2];
383 for (i = 0; i < size/BLOCK_SIZE; i++)
385 rb->memcpy(&block[0], &buf[i*BLOCK_SIZE], sizeof(block));
387 /* byte swap one block */
388 block[0] = htole32(block[0]);
389 block[1] = htole32(block[1]);
391 encrypt(&block[0], key);
393 block[0] = htole32(block[0]);
394 block[1] = htole32(block[1]);
396 rb->memcpy(&buf[i*BLOCK_SIZE], &block[0], sizeof(block));
400 static int parse_buffer(void)
402 int i;
403 int len;
404 struct pw_entry *entry = pw_list.first.next;
405 char *start, *end;
406 start = &buffer[HEADER_LEN];
408 rb->memcpy(&salt, &buffer[0], sizeof(salt));
409 make_key();
411 decrypt_buffer(&buffer[8], bytes_read - 8, &key.words[0]);
413 if (rb->memcmp(&buffer[8], &pwhash, sizeof(union hash)))
415 rb->splash(HZ*2, "Wrong password");
416 return -1;
419 for (i = 0; i < MAX_ENTRIES; i++)
421 end = rb->strchr(start, '\0'); /* find eol */
422 len = end - &buffer[HEADER_LEN];
423 if ((len > bytes_read + HEADER_LEN) || start == end)
425 break;
428 rb->strlcpy(entry->title, start, FIELD_LEN);
429 start = end + 1;
431 end = rb->strchr(start, '\0'); /* find eol */
432 len = end - &buffer[HEADER_LEN];
433 if (len > bytes_read + HEADER_LEN)
435 break;
438 rb->strlcpy(entry->name, start, FIELD_LEN);
439 start = end + 1;
441 end = rb->strchr(start, '\0'); /* find eol */
442 len = end - &buffer[HEADER_LEN];
443 if (len > bytes_read + HEADER_LEN)
445 break;
447 rb->strlcpy(entry->password, start, FIELD_LEN);
448 start = end + 1;
449 entry->used = true;
450 if (i + 1 < MAX_ENTRIES - 1)
452 entry->next = &pw_list.entries[i+1];
453 entry = entry->next;
455 else
457 break;
460 entry->next = NULL;
461 pw_list.num_entries = i;
462 rb->gui_synclist_set_nb_items(&kb_list, pw_list.num_entries);
463 return 0;
466 static void write_output(int fd)
468 size_t bytes_written;
469 int i;
470 size_t len, size;
471 char *p = &buffer[HEADER_LEN]; /* reserve space for salt + hash */
473 rb->memcpy(&buffer[8], &pwhash, sizeof(union hash));
474 struct pw_entry *entry = pw_list.first.next;
476 for (i = 0; i < pw_list.num_entries; i++)
478 len = rb->strlen(entry->title);
479 rb->strlcpy(p, entry->title, len+1);
480 p += len+1;
481 len = rb->strlen(entry->name);
482 rb->strlcpy(p, entry->name, len+1);
483 p += len+1;
484 len = rb->strlen(entry->password);
485 rb->strlcpy(p, entry->password, len+1);
486 p += len+1;
487 if (entry->next)
488 entry = entry->next;
490 *p++ = '\0'; /* mark the end of the list */
492 /* round up to a number divisible by BLOCK_SIZE */
493 size = ((p - buffer + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE;
495 salt = rb->rand();
496 make_key();
498 encrypt_buffer(&buffer[8], size, &key.words[0]);
499 rb->memcpy(&buffer[0], &salt, sizeof(salt));
501 bytes_written = rb->write(fd, &buffer, size);
504 static int enter_pw(char *pw_buf, size_t buflen, bool new_pw)
506 char buf[2][sizeof(master_pw)];
507 rb->memset(buf, 0, sizeof(buf));
508 rb->memset(master_pw, 0, sizeof(master_pw));
510 if (new_pw)
512 rb->splash(HZ, "Enter new master password");
513 if (rb->kbd_input(buf[0], sizeof(buf[0])) < 0)
514 return -1;
516 rb->splash(HZ, "Confirm master password");
517 if (rb->kbd_input(buf[1], sizeof(buf[1])) < 0)
518 return -1;
520 if (rb->strcmp(buf[0], buf[1]))
522 rb->splash(HZ, "Password mismatch");
523 return -1;
525 else
527 rb->strlcpy(pw_buf, buf[0], buflen);
528 hash_pw(&pwhash);
529 return 0;
533 rb->splash(HZ, "Enter master password");
534 if (rb->kbd_input(pw_buf, buflen) < 0)
535 return -1;
536 hash_pw(&pwhash);
537 return 0;
540 static int keybox(void)
542 int button, fd;
543 bool new_file = !rb->file_exists(KEYBOX_FILE);
544 bool done = false;
546 if (enter_pw(master_pw, sizeof (master_pw), new_file))
547 return 0;
549 /* Read the existing file */
550 if (!new_file)
552 fd = rb->open(KEYBOX_FILE, O_RDONLY);
553 if (fd < 0)
554 return FILE_OPEN_ERROR;
555 bytes_read = rb->read(fd, &buffer, sizeof(buffer));
557 rb->close(fd);
559 if (parse_buffer())
560 return 0;
563 while (!done)
565 rb->gui_synclist_draw(&kb_list);
566 button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
567 if (rb->gui_synclist_do_button(&kb_list, &button, LIST_WRAP_ON))
568 continue;
570 switch (button)
572 case ACTION_STD_OK:
573 splash_pw(rb->gui_synclist_get_sel_pos(&kb_list));
574 break;
575 case ACTION_STD_CONTEXT:
576 context_menu(rb->gui_synclist_get_sel_pos(&kb_list));
577 break;
578 case ACTION_STD_CANCEL:
579 done = true;
580 break;
582 rb->yield();
585 if (data_changed)
587 fd = rb->open(KEYBOX_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0666);
588 if (fd < 0)
589 return FILE_OPEN_ERROR;
590 write_output(fd);
591 rb->close(fd);
594 return 0;
597 static void reset(void)
599 static const char *message_lines[]=
600 {"Do you really want", "to reset keybox?"};
601 static const char *yes_lines[]=
602 {"Keybox reset."};
603 static const struct text_message message={message_lines, 2};
604 static const struct text_message yes_message={yes_lines, 1};
606 if(rb->gui_syncyesno_run(&message, &yes_message, NULL) == YESNO_YES)
608 rb->remove(KEYBOX_FILE);
609 rb->memset(&buffer, 0, sizeof(buffer));
610 rb->memset(&pw_list, 0, sizeof(pw_list));
611 rb->gui_synclist_set_nb_items(&kb_list, 0);
612 init_ll();
616 static int main_menu(void)
618 int selection = 0, result, ret;
619 bool exit = false;
621 MENUITEM_STRINGLIST(menu, "Keybox", NULL,
622 "Enter Keybox", "Reset Keybox",
623 "Playback Control", "Exit");
625 do {
626 result = rb->do_menu(&menu, &selection, NULL, false);
627 switch (result) {
628 case 0:
629 ret = keybox();
630 if (ret)
631 return ret;
632 break;
633 case 1:
634 reset();
635 break;
636 case 2:
637 playback_control(NULL);
638 break;
639 case 3:
640 exit = true;
641 break;
643 rb->yield();
644 } while (!exit);
646 return 0;
649 enum plugin_status plugin_start(const void *parameter)
651 (void)parameter;
652 int ret;
654 rb->gui_synclist_init(&kb_list, &kb_list_cb, NULL, false, 1, NULL);
656 rb->gui_synclist_set_title(&kb_list, "Keybox", NOICON);
657 rb->gui_synclist_set_icon_callback(&kb_list, NULL);
658 rb->gui_synclist_set_nb_items(&kb_list, 0);
659 rb->gui_synclist_limit_scroll(&kb_list, false);
660 rb->gui_synclist_select_item(&kb_list, 0);
662 init_ll();
663 ret = main_menu();
665 switch (ret)
667 case FILE_OPEN_ERROR:
668 rb->splash(HZ*2, "Error opening file");
669 return PLUGIN_ERROR;
672 return PLUGIN_OK;