summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranklin Wei <frankhwei536@gmail.com>2016-05-25 21:43:32 -0400
committerFranklin Wei <frankhwei536@gmail.com>2016-06-05 14:25:09 -0400
commit30d7ead6af5c28ff72d6e47bab7e044657be7ce6 (patch)
tree57b38d872bb37cf361fb2777ba5da48d5fc14a3c
parent59ae562a3280105595e690ebff772ea4f7790970 (diff)
downloadrockbox-30d7ead6af5c28ff72d6e47bab7e044657be7ce6.tar.gz
rockbox-30d7ead6af5c28ff72d6e47bab7e044657be7ce6.zip
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
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/lib/SOURCES1
-rw-r--r--apps/plugins/lib/sha1.c434
-rw-r--r--apps/plugins/lib/sha1.h116
-rw-r--r--apps/plugins/otp.c1095
-rw-r--r--manual/configure_rockbox/time_and_date.tex2
-rw-r--r--manual/plugins/main.tex2
-rw-r--r--manual/plugins/otp.tex72
9 files changed, 1724 insertions, 0 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index fd7a49af8f..28248802b7 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -65,6 +65,7 @@ mp3_encoder,apps
65mpegplayer,viewers 65mpegplayer,viewers
66nim,games 66nim,games
67oscilloscope,demos 67oscilloscope,demos
68otp,apps
68pacbox,games 69pacbox,games
69pdbox,viewers 70pdbox,viewers
70pegbox,games 71pegbox,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 3865fbf85e..c7a8cb69f7 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -33,6 +33,7 @@ disktidy.c
33flipit.c 33flipit.c
34shopper.c 34shopper.c
35resistor.c 35resistor.c
36otp.c
36 37
37#ifdef USB_ENABLE_HID 38#ifdef USB_ENABLE_HID
38remote_control.c 39remote_control.c
diff --git a/apps/plugins/lib/SOURCES b/apps/plugins/lib/SOURCES
index 0a125dbbe4..747355b44b 100644
--- a/apps/plugins/lib/SOURCES
+++ b/apps/plugins/lib/SOURCES
@@ -1,3 +1,4 @@
1sha1.c
1gcc-support.c 2gcc-support.c
2pluginlib_actions.c 3pluginlib_actions.c
3helper.c 4helper.c
diff --git a/apps/plugins/lib/sha1.c b/apps/plugins/lib/sha1.c
new file mode 100644
index 0000000000..107c50256b
--- /dev/null
+++ b/apps/plugins/lib/sha1.c
@@ -0,0 +1,434 @@
1/* sha1.c - Functions to compute SHA1 message digest of files or
2 memory blocks according to the NIST specification FIPS-180-1.
3
4 Copyright (C) 2000, 2001, 2003, 2004, 2005, 2006 Free Software
5 Foundation, Inc.
6
7 This program is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; either version 2, or (at your option) any
10 later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software Foundation,
19 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
20
21/* Written by Scott G. Miller
22 Credits:
23 Robert Klep <robert@ilse.nl> -- Expansion function fix
24*/
25
26#include "plugin.h"
27#include "sha1.h"
28
29#ifdef WORDS_BIGENDIAN
30# define SWAP(n) (n)
31#else
32# define SWAP(n) \
33 (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
34#endif
35
36#define BLOCKSIZE 4096
37#if BLOCKSIZE % 64 != 0
38# error "invalid BLOCKSIZE"
39#endif
40
41/* This array contains the bytes used to pad the buffer to the next
42 64-byte boundary. (RFC 1321, 3.1: Step 1) */
43static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
44
45
46/* Take a pointer to a 160 bit block of data (five 32 bit ints) and
47 initialize it to the start constants of the SHA1 algorithm. This
48 must be called before using hash in the call to sha1_hash. */
49void
50sha1_init_ctx (struct sha1_ctx *ctx)
51{
52 ctx->A = 0x67452301;
53 ctx->B = 0xefcdab89;
54 ctx->C = 0x98badcfe;
55 ctx->D = 0x10325476;
56 ctx->E = 0xc3d2e1f0;
57
58 ctx->total[0] = ctx->total[1] = 0;
59 ctx->buflen = 0;
60}
61
62/* Put result from CTX in first 20 bytes following RESBUF. The result
63 must be in little endian byte order.
64
65 IMPORTANT: On some systems it is required that RESBUF is correctly
66 aligned for a 32-bit value. */
67void *
68sha1_read_ctx (const struct sha1_ctx *ctx, void *resbuf)
69{
70 ((uint32_t *) resbuf)[0] = SWAP (ctx->A);
71 ((uint32_t *) resbuf)[1] = SWAP (ctx->B);
72 ((uint32_t *) resbuf)[2] = SWAP (ctx->C);
73 ((uint32_t *) resbuf)[3] = SWAP (ctx->D);
74 ((uint32_t *) resbuf)[4] = SWAP (ctx->E);
75
76 return resbuf;
77}
78
79/* Process the remaining bytes in the internal buffer and the usual
80 prolog according to the standard and write the result to RESBUF.
81
82 IMPORTANT: On some systems it is required that RESBUF is correctly
83 aligned for a 32-bit value. */
84void *
85sha1_finish_ctx (struct sha1_ctx *ctx, void *resbuf)
86{
87 /* Take yet unprocessed bytes into account. */
88 uint32_t bytes = ctx->buflen;
89 size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;
90
91 /* Now count remaining bytes. */
92 ctx->total[0] += bytes;
93 if (ctx->total[0] < bytes)
94 ++ctx->total[1];
95
96 /* Put the 64-bit file length in *bits* at the end of the buffer. */
97 ctx->buffer[size - 2] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29));
98 ctx->buffer[size - 1] = SWAP (ctx->total[0] << 3);
99
100 memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
101
102 /* Process last bytes. */
103 sha1_process_block (ctx->buffer, size * 4, ctx);
104
105 return sha1_read_ctx (ctx, resbuf);
106}
107
108/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The
109 result is always in little endian byte order, so that a byte-wise
110 output yields to the wanted ASCII representation of the message
111 digest. */
112void *sha1_buffer (const char *buffer, size_t len, void *resblock)
113{
114 struct sha1_ctx ctx;
115
116 /* Initialize the computation context. */
117 sha1_init_ctx (&ctx);
118
119 /* Process whole buffer but last len % 64 bytes. */
120 sha1_process_bytes (buffer, len, &ctx);
121
122 /* Put result in desired memory area. */
123 return sha1_finish_ctx (&ctx, resblock);
124}
125
126void
127sha1_process_bytes (const void *buffer, size_t len, struct sha1_ctx *ctx)
128{
129 /* When we already have some bits in our internal buffer concatenate
130 both inputs first. */
131 if (ctx->buflen != 0)
132 {
133 size_t left_over = ctx->buflen;
134 size_t add = 128 - left_over > len ? len : 128 - left_over;
135
136 memcpy (&((char *) ctx->buffer)[left_over], buffer, add);
137 ctx->buflen += add;
138
139 if (ctx->buflen > 64)
140 {
141 sha1_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
142
143 ctx->buflen &= 63;
144 /* The regions in the following copy operation cannot overlap. */
145 memcpy (ctx->buffer,
146 &((char *) ctx->buffer)[(left_over + add) & ~63],
147 ctx->buflen);
148 }
149
150 buffer = (const char *) buffer + add;
151 len -= add;
152 }
153
154 /* Process available complete blocks. */
155 if (len >= 64)
156 {
157 {
158 sha1_process_block (buffer, len & ~63, ctx);
159 buffer = (const char *) buffer + (len & ~63);
160 len &= 63;
161 }
162 }
163
164 /* Move remaining bytes in internal buffer. */
165 if (len > 0)
166 {
167 size_t left_over = ctx->buflen;
168
169 memcpy (&((char *) ctx->buffer)[left_over], buffer, len);
170 left_over += len;
171 if (left_over >= 64)
172 {
173 sha1_process_block (ctx->buffer, 64, ctx);
174 left_over -= 64;
175 memcpy (ctx->buffer, &ctx->buffer[16], left_over);
176 }
177 ctx->buflen = left_over;
178 }
179}
180
181/* --- Code below is the primary difference between md5.c and sha1.c --- */
182
183/* SHA1 round constants */
184#define K1 0x5a827999
185#define K2 0x6ed9eba1
186#define K3 0x8f1bbcdc
187#define K4 0xca62c1d6
188
189/* Round functions. Note that F2 is the same as F4. */
190#define F1(B,C,D) ( D ^ ( B & ( C ^ D ) ) )
191#define F2(B,C,D) (B ^ C ^ D)
192#define F3(B,C,D) ( ( B & C ) | ( D & ( B | C ) ) )
193#define F4(B,C,D) (B ^ C ^ D)
194
195/* Process LEN bytes of BUFFER, accumulating context into CTX.
196 It is assumed that LEN % 64 == 0.
197 Most of this code comes from GnuPG's cipher/sha1.c. */
198
199void
200sha1_process_block (const void *buffer, size_t len, struct sha1_ctx *ctx)
201{
202 const uint32_t *words = buffer;
203 size_t nwords = len / sizeof (uint32_t);
204 const uint32_t *endp = words + nwords;
205 uint32_t x[16];
206 uint32_t a = ctx->A;
207 uint32_t b = ctx->B;
208 uint32_t c = ctx->C;
209 uint32_t d = ctx->D;
210 uint32_t e = ctx->E;
211
212 /* First increment the byte count. RFC 1321 specifies the possible
213 length of the file up to 2^64 bits. Here we only compute the
214 number of bytes. Do a double word increment. */
215 ctx->total[0] += len;
216 if (ctx->total[0] < len)
217 ++ctx->total[1];
218
219#define rol(x, n) (((x) << (n)) | ((uint32_t) (x) >> (32 - (n))))
220
221#define M(I) ( tm = x[I&0x0f] ^ x[(I-14)&0x0f] \
222 ^ x[(I-8)&0x0f] ^ x[(I-3)&0x0f] \
223 , (x[I&0x0f] = rol(tm, 1)) )
224
225#define R(A,B,C,D,E,F,K,M) do { E += rol( A, 5 ) \
226 + F( B, C, D ) \
227 + K \
228 + M; \
229 B = rol( B, 30 ); \
230 } while(0)
231
232 while (words < endp)
233 {
234 uint32_t tm;
235 int t;
236 for (t = 0; t < 16; t++)
237 {
238 x[t] = SWAP (*words);
239 words++;
240 }
241
242 R( a, b, c, d, e, F1, K1, x[ 0] );
243 R( e, a, b, c, d, F1, K1, x[ 1] );
244 R( d, e, a, b, c, F1, K1, x[ 2] );
245 R( c, d, e, a, b, F1, K1, x[ 3] );
246 R( b, c, d, e, a, F1, K1, x[ 4] );
247 R( a, b, c, d, e, F1, K1, x[ 5] );
248 R( e, a, b, c, d, F1, K1, x[ 6] );
249 R( d, e, a, b, c, F1, K1, x[ 7] );
250 R( c, d, e, a, b, F1, K1, x[ 8] );
251 R( b, c, d, e, a, F1, K1, x[ 9] );
252 R( a, b, c, d, e, F1, K1, x[10] );
253 R( e, a, b, c, d, F1, K1, x[11] );
254 R( d, e, a, b, c, F1, K1, x[12] );
255 R( c, d, e, a, b, F1, K1, x[13] );
256 R( b, c, d, e, a, F1, K1, x[14] );
257 R( a, b, c, d, e, F1, K1, x[15] );
258 R( e, a, b, c, d, F1, K1, M(16) );
259 R( d, e, a, b, c, F1, K1, M(17) );
260 R( c, d, e, a, b, F1, K1, M(18) );
261 R( b, c, d, e, a, F1, K1, M(19) );
262 R( a, b, c, d, e, F2, K2, M(20) );
263 R( e, a, b, c, d, F2, K2, M(21) );
264 R( d, e, a, b, c, F2, K2, M(22) );
265 R( c, d, e, a, b, F2, K2, M(23) );
266 R( b, c, d, e, a, F2, K2, M(24) );
267 R( a, b, c, d, e, F2, K2, M(25) );
268 R( e, a, b, c, d, F2, K2, M(26) );
269 R( d, e, a, b, c, F2, K2, M(27) );
270 R( c, d, e, a, b, F2, K2, M(28) );
271 R( b, c, d, e, a, F2, K2, M(29) );
272 R( a, b, c, d, e, F2, K2, M(30) );
273 R( e, a, b, c, d, F2, K2, M(31) );
274 R( d, e, a, b, c, F2, K2, M(32) );
275 R( c, d, e, a, b, F2, K2, M(33) );
276 R( b, c, d, e, a, F2, K2, M(34) );
277 R( a, b, c, d, e, F2, K2, M(35) );
278 R( e, a, b, c, d, F2, K2, M(36) );
279 R( d, e, a, b, c, F2, K2, M(37) );
280 R( c, d, e, a, b, F2, K2, M(38) );
281 R( b, c, d, e, a, F2, K2, M(39) );
282 R( a, b, c, d, e, F3, K3, M(40) );
283 R( e, a, b, c, d, F3, K3, M(41) );
284 R( d, e, a, b, c, F3, K3, M(42) );
285 R( c, d, e, a, b, F3, K3, M(43) );
286 R( b, c, d, e, a, F3, K3, M(44) );
287 R( a, b, c, d, e, F3, K3, M(45) );
288 R( e, a, b, c, d, F3, K3, M(46) );
289 R( d, e, a, b, c, F3, K3, M(47) );
290 R( c, d, e, a, b, F3, K3, M(48) );
291 R( b, c, d, e, a, F3, K3, M(49) );
292 R( a, b, c, d, e, F3, K3, M(50) );
293 R( e, a, b, c, d, F3, K3, M(51) );
294 R( d, e, a, b, c, F3, K3, M(52) );
295 R( c, d, e, a, b, F3, K3, M(53) );
296 R( b, c, d, e, a, F3, K3, M(54) );
297 R( a, b, c, d, e, F3, K3, M(55) );
298 R( e, a, b, c, d, F3, K3, M(56) );
299 R( d, e, a, b, c, F3, K3, M(57) );
300 R( c, d, e, a, b, F3, K3, M(58) );
301 R( b, c, d, e, a, F3, K3, M(59) );
302 R( a, b, c, d, e, F4, K4, M(60) );
303 R( e, a, b, c, d, F4, K4, M(61) );
304 R( d, e, a, b, c, F4, K4, M(62) );
305 R( c, d, e, a, b, F4, K4, M(63) );
306 R( b, c, d, e, a, F4, K4, M(64) );
307 R( a, b, c, d, e, F4, K4, M(65) );
308 R( e, a, b, c, d, F4, K4, M(66) );
309 R( d, e, a, b, c, F4, K4, M(67) );
310 R( c, d, e, a, b, F4, K4, M(68) );
311 R( b, c, d, e, a, F4, K4, M(69) );
312 R( a, b, c, d, e, F4, K4, M(70) );
313 R( e, a, b, c, d, F4, K4, M(71) );
314 R( d, e, a, b, c, F4, K4, M(72) );
315 R( c, d, e, a, b, F4, K4, M(73) );
316 R( b, c, d, e, a, F4, K4, M(74) );
317 R( a, b, c, d, e, F4, K4, M(75) );
318 R( e, a, b, c, d, F4, K4, M(76) );
319 R( d, e, a, b, c, F4, K4, M(77) );
320 R( c, d, e, a, b, F4, K4, M(78) );
321 R( b, c, d, e, a, F4, K4, M(79) );
322
323 a = ctx->A += a;
324 b = ctx->B += b;
325 c = ctx->C += c;
326 d = ctx->D += d;
327 e = ctx->E += e;
328 }
329}
330
331/* memxor.c -- perform binary exclusive OR operation of two memory blocks.
332 Copyright (C) 2005, 2006 Free Software Foundation, Inc.
333
334 This program is free software; you can redistribute it and/or modify
335 it under the terms of the GNU General Public License as published by
336 the Free Software Foundation; either version 2, or (at your option)
337 any later version.
338
339 This program is distributed in the hope that it will be useful,
340 but WITHOUT ANY WARRANTY; without even the implied warranty of
341 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
342 GNU General Public License for more details.
343
344 You should have received a copy of the GNU General Public License
345 along with this program; if not, write to the Free Software Foundation,
346 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
347
348/* Written by Simon Josefsson. The interface was inspired by memxor
349 in Niels Möller's Nettle. */
350
351void *
352memxor (void * dest, const void * src, size_t n)
353{
354 char const *s = src;
355 char *d = dest;
356
357 for (; n > 0; n--)
358 *d++ ^= *s++;
359
360 return dest;
361}
362
363/* hmac-sha1.c -- hashed message authentication codes
364 Copyright (C) 2005, 2006 Free Software Foundation, Inc.
365
366 This program is free software; you can redistribute it and/or modify
367 it under the terms of the GNU General Public License as published by
368 the Free Software Foundation; either version 2, or (at your option)
369 any later version.
370
371 This program is distributed in the hope that it will be useful,
372 but WITHOUT ANY WARRANTY; without even the implied warranty of
373 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
374 GNU General Public License for more details.
375
376 You should have received a copy of the GNU General Public License
377 along with this program; if not, write to the Free Software Foundation,
378 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
379
380/* Written by Simon Josefsson. */
381
382#define IPAD 0x36
383#define OPAD 0x5c
384
385int
386hmac_sha1 (const void *key, size_t keylen,
387 const void *in, size_t inlen, void *resbuf)
388{
389 struct sha1_ctx inner;
390 struct sha1_ctx outer;
391 char optkeybuf[20];
392 char block[64];
393 char innerhash[20];
394
395 /* Reduce the key's size, so that it becomes <= 64 bytes large. */
396
397 if (keylen > 64)
398 {
399 struct sha1_ctx keyhash;
400
401 sha1_init_ctx (&keyhash);
402 sha1_process_bytes (key, keylen, &keyhash);
403 sha1_finish_ctx (&keyhash, optkeybuf);
404
405 key = optkeybuf;
406 keylen = 20;
407 }
408
409 /* Compute INNERHASH from KEY and IN. */
410
411 sha1_init_ctx (&inner);
412
413 memset (block, IPAD, sizeof (block));
414 memxor (block, key, keylen);
415
416 sha1_process_block (block, 64, &inner);
417 sha1_process_bytes (in, inlen, &inner);
418
419 sha1_finish_ctx (&inner, innerhash);
420
421 /* Compute result from KEY and INNERHASH. */
422
423 sha1_init_ctx (&outer);
424
425 memset (block, OPAD, sizeof (block));
426 memxor (block, key, keylen);
427
428 sha1_process_block (block, 64, &outer);
429 sha1_process_bytes (innerhash, 20, &outer);
430
431 sha1_finish_ctx (&outer, resbuf);
432
433 return 0;
434}
diff --git a/apps/plugins/lib/sha1.h b/apps/plugins/lib/sha1.h
new file mode 100644
index 0000000000..6358d046d3
--- /dev/null
+++ b/apps/plugins/lib/sha1.h
@@ -0,0 +1,116 @@
1/* Taken from gnulib (http://savannah.gnu.org/projects/gnulib/) */
2/* Declarations of functions and data types used for SHA1 sum
3 library functions.
4 Copyright (C) 2000, 2001, 2003, 2005, 2006 Free Software Foundation, Inc.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software Foundation,
18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19
20#ifndef SHA1_H
21#define SHA1_H 1
22
23#include "plugin.h"
24
25/* Structure to save state of computation between the single steps. */
26struct sha1_ctx
27{
28 uint32_t A;
29 uint32_t B;
30 uint32_t C;
31 uint32_t D;
32 uint32_t E;
33
34 uint32_t total[2];
35 uint32_t buflen;
36 uint32_t buffer[32];
37};
38
39
40/* Initialize structure containing state of computation. */
41void sha1_init_ctx (struct sha1_ctx *ctx);
42
43/* Starting with the result of former calls of this function (or the
44 initialization function update the context for the next LEN bytes
45 starting at BUFFER.
46 It is necessary that LEN is a multiple of 64!!! */
47void sha1_process_block (const void *buffer, size_t len,
48 struct sha1_ctx *ctx);
49
50/* Starting with the result of former calls of this function (or the
51 initialization function update the context for the next LEN bytes
52 starting at BUFFER.
53 It is NOT required that LEN is a multiple of 64. */
54void sha1_process_bytes (const void *buffer, size_t len,
55 struct sha1_ctx *ctx);
56
57/* Process the remaining bytes in the buffer and put result from CTX
58 in first 20 bytes following RESBUF. The result is always in little
59 endian byte order, so that a byte-wise output yields to the wanted
60 ASCII representation of the message digest.
61
62 IMPORTANT: On some systems it is required that RESBUF be correctly
63 aligned for a 32 bits value. */
64void *sha1_finish_ctx (struct sha1_ctx *ctx, void *resbuf);
65
66
67/* Put result from CTX in first 20 bytes following RESBUF. The result is
68 always in little endian byte order, so that a byte-wise output yields
69 to the wanted ASCII representation of the message digest.
70
71 IMPORTANT: On some systems it is required that RESBUF is correctly
72 aligned for a 32 bits value. */
73void *sha1_read_ctx (const struct sha1_ctx *ctx, void *resbuf);
74
75/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The
76 result is always in little endian byte order, so that a byte-wise
77 output yields to the wanted ASCII representation of the message
78 digest. */
79void *sha1_buffer (const char *buffer, size_t len, void *resblock);
80
81#endif
82
83
84/* hmac.h -- hashed message authentication codes
85 Copyright (C) 2005 Free Software Foundation, Inc.
86
87 This program is free software; you can redistribute it and/or modify
88 it under the terms of the GNU General Public License as published by
89 the Free Software Foundation; either version 2, or (at your option)
90 any later version.
91
92 This program is distributed in the hope that it will be useful,
93 but WITHOUT ANY WARRANTY; without even the implied warranty of
94 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
95 GNU General Public License for more details.
96
97 You should have received a copy of the GNU General Public License
98 along with this program; if not, write to the Free Software Foundation,
99 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
100
101/* Written by Simon Josefsson. */
102
103#ifndef HMAC_H
104#define HMAC_H 1
105
106#include <stddef.h>
107
108/* Compute Hashed Message Authentication Code with SHA-1, over BUFFER
109 data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
110 output to pre-allocated 20 byte minimum RESBUF buffer. Return 0 on
111 success. */
112int
113hmac_sha1 (const void *key, size_t keylen,
114 const void *in, size_t inlen, void *resbuf);
115
116#endif /* HMAC_H */
diff --git a/apps/plugins/otp.c b/apps/plugins/otp.c
new file mode 100644
index 0000000000..69cb8b7982
--- /dev/null
+++ b/apps/plugins/otp.c
@@ -0,0 +1,1095 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 Franklin Wei
11 *
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.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/* simple OTP plugin */
23
24/* see RFCs 4226, 6238 for more information about the algorithms used */
25
26#include "plugin.h"
27
28#include "lib/display_text.h"
29#include "lib/pluginlib_actions.h"
30#include "lib/pluginlib_exit.h"
31#include "lib/sha1.h"
32
33#define MAX_NAME 50
34#define SECRET_MAX 256
35#define URI_MAX 256
36#define ACCT_FILE PLUGIN_APPS_DATA_DIR "/otp.dat"
37
38#define MAX(a, b) (((a)>(b))?(a):(b))
39
40struct account_t {
41 char name[MAX_NAME];
42
43 bool is_totp; // hotp otherwise
44
45 union {
46 uint64_t hotp_counter;
47 int totp_period;
48 };
49
50 int digits;
51
52 unsigned char secret[SECRET_MAX];
53 int sec_len;
54};
55
56static int max_accts = 0;
57
58/* in plugin buffer */
59static struct account_t *accounts = NULL;
60
61static int next_slot = 0;
62
63/* in SECONDS, asked for on first run */
64static int time_offs = 0;
65
66static int HOTP(unsigned char *secret, size_t sec_len, uint64_t ctr, int digits)
67{
68 ctr = htobe64(ctr);
69 unsigned char hash[20];
70 if(hmac_sha1(secret, sec_len, &ctr, 8, hash))
71 {
72 return -1;
73 }
74
75 int offs = hash[19] & 0xF;
76 uint32_t code = (hash[offs] & 0x7F) << 24 |
77 hash[offs + 1] << 16 |
78 hash[offs + 2] << 8 |
79 hash[offs + 3];
80
81 int mod = 1;
82 for(int i = 0; i < digits; ++i)
83 mod *= 10;
84
85 // debug
86 // rb->splashf(HZ * 5, "HOTP %*s, %llu, %d: %d", sec_len, secret, htobe64(ctr), digits, code % mod);
87
88 return code % mod;
89}
90
91#if CONFIG_RTC
92static time_t get_utc(void)
93{
94 return rb->mktime(rb->get_time()) - time_offs;
95}
96
97static int TOTP(unsigned char *secret, size_t sec_len, uint64_t step, int digits)
98{
99 uint64_t tm = get_utc() / step;
100 return HOTP(secret, sec_len, tm, digits);
101}
102#endif
103
104/* search the accounts for a duplicate */
105static bool acct_exists(const char *name)
106{
107 for(int i = 0; i < next_slot; ++i)
108 if(!rb->strcmp(accounts[i].name, name))
109 return true;
110 return false;
111}
112
113// Base32 implementation
114//
115// Copyright 2010 Google Inc.
116// Author: Markus Gutschke
117//
118// Licensed under the Apache License, Version 2.0 (the "License");
119// you may not use this file except in compliance with the License.
120// You may obtain a copy of the License at
121//
122// http://www.apache.org/licenses/LICENSE-2.0
123//
124// Unless required by applicable law or agreed to in writing, software
125// distributed under the License is distributed on an "AS IS" BASIS,
126// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
127// See the License for the specific language governing permissions and
128// limitations under the License.
129
130static int base32_decode(uint8_t *result, int bufSize, const uint8_t *encoded) {
131 int buffer = 0;
132 int bitsLeft = 0;
133 int count = 0;
134 for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) {
135 uint8_t ch = *ptr;
136 if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') {
137 continue;
138 }
139 buffer <<= 5;
140
141 // Deal with commonly mistyped characters
142 if (ch == '0') {
143 ch = 'O';
144 } else if (ch == '1') {
145 ch = 'L';
146 } else if (ch == '8') {
147 ch = 'B';
148 }
149
150 // Look up one base32 digit
151 if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
152 ch = (ch & 0x1F) - 1;
153 } else if (ch >= '2' && ch <= '7') {
154 ch -= '2' - 26;
155 } else {
156 return -1;
157 }
158
159 buffer |= ch;
160 bitsLeft += 5;
161 if (bitsLeft >= 8) {
162 result[count++] = buffer >> (bitsLeft - 8);
163 bitsLeft -= 8;
164 }
165 }
166 if (count < bufSize) {
167 result[count] = '\000';
168 }
169 return count;
170}
171
172static int base32_encode(const uint8_t *data, int length, uint8_t *result,
173 int bufSize) {
174 if (length < 0 || length > (1 << 28)) {
175 return -1;
176 }
177 int count = 0;
178 if (length > 0) {
179 int buffer = data[0];
180 int next = 1;
181 int bitsLeft = 8;
182 while (count < bufSize && (bitsLeft > 0 || next < length)) {
183 if (bitsLeft < 5) {
184 if (next < length) {
185 buffer <<= 8;
186 buffer |= data[next++] & 0xFF;
187 bitsLeft += 8;
188 } else {
189 int pad = 5 - bitsLeft;
190 buffer <<= pad;
191 bitsLeft += pad;
192 }
193 }
194 int index = 0x1F & (buffer >> (bitsLeft - 5));
195 bitsLeft -= 5;
196 result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index];
197 }
198 }
199 if (count < bufSize) {
200 result[count] = '\000';
201 }
202 return count;
203}
204
205/***********************************************************************
206 * File browser (from rockpaint)
207 ***********************************************************************/
208
209static bool browse( char *dst, int dst_size, const char *start )
210{
211 struct browse_context browse;
212
213 rb->browse_context_init(&browse, SHOW_ALL,
214 BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU,
215 NULL, NOICON, start, NULL);
216
217 browse.buf = dst;
218 browse.bufsize = dst_size;
219
220 rb->rockbox_browse(&browse);
221
222 return (browse.flags & BROWSE_SELECTED);
223}
224
225static bool read_accts(void)
226{
227 int fd = rb->open(ACCT_FILE, O_RDONLY);
228 if(fd < 0)
229 return false;
230
231 char buf[4];
232 char magic[4] = { 'O', 'T', 'P', '1' };
233 rb->read(fd, buf, 4);
234 if(memcmp(magic, buf, 4))
235 {
236 rb->splash(HZ * 2, "Corrupt save data!");
237 rb->close(fd);
238 return false;
239 }
240
241 rb->read(fd, &time_offs, sizeof(time_offs));
242
243 while(next_slot < max_accts)
244 {
245 if(rb->read(fd, accounts + next_slot, sizeof(struct account_t)) != sizeof(struct account_t))
246 break;
247 ++next_slot;
248 }
249
250 rb->close(fd);
251 return true;
252}
253
254static void save_accts(void)
255{
256 int fd = rb->open(ACCT_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0600);
257 rb->fdprintf(fd, "OTP1");
258
259 rb->write(fd, &time_offs, sizeof(time_offs));
260
261 for(int i = 0; i < next_slot; ++i)
262 rb->write(fd, accounts + i, sizeof(struct account_t));
263 rb->close(fd);
264}
265
266static void add_acct_file(void)
267{
268 char fname[MAX_PATH];
269 rb->splash(HZ * 2, "Please choose file containing URI(s).");
270 int before = next_slot;
271 if(browse(fname, sizeof(fname), "/"))
272 {
273 int fd = rb->open(fname, O_RDONLY);
274 do {
275 memset(accounts + next_slot, 0, sizeof(struct account_t));
276
277 accounts[next_slot].digits = 6;
278
279 char uri_buf[URI_MAX];
280 if(!rb->read_line(fd, uri_buf, sizeof(uri_buf)))
281 break;
282
283 if(next_slot >= max_accts)
284 {
285 rb->splash(HZ * 2, "Account limit reached: some accounts not added.");
286 break;
287 }
288
289 /* check for URI prefix */
290 if(rb->strncmp(uri_buf, "otpauth://", 10))
291 continue;
292
293 char *save;
294 char *tok = rb->strtok_r(uri_buf + 10, "/", &save);
295 if(!rb->strcmp(tok, "totp"))
296 {
297 accounts[next_slot].is_totp = true;
298 accounts[next_slot].totp_period = 30;
299#if !CONFIG_RTC
300 rb->splash(2 * HZ, "TOTP not supported!");
301 continue;
302#endif
303 }
304 else if(!rb->strcmp(tok, "hotp"))
305 {
306 accounts[next_slot].is_totp = false;
307 accounts[next_slot].hotp_counter = 0;
308 }
309
310 tok = rb->strtok_r(NULL, "?", &save);
311 if(!tok)
312 continue;
313
314 if(acct_exists(tok))
315 {
316 rb->splashf(HZ * 2, "Not adding account with duplicate name `%s'!", tok);
317 continue;
318 }
319
320 if(!rb->strlen(tok))
321 {
322 rb->splashf(HZ * 2, "Skipping account with empty name.");
323 continue;
324 }
325
326 rb->strlcpy(accounts[next_slot].name, tok, sizeof(accounts[next_slot].name));
327
328 bool have_secret = false;
329
330 do {
331 tok = rb->strtok_r(NULL, "=", &save);
332 if(!tok)
333 continue;
334
335 if(!rb->strcmp(tok, "secret"))
336 {
337 if(have_secret)
338 {
339 rb->splashf(HZ * 2, "URI with multiple `secret' parameters found, skipping!");
340 goto fail;
341 }
342 have_secret = true;
343 tok = rb->strtok_r(NULL, "&", &save);
344 if((accounts[next_slot].sec_len = base32_decode(accounts[next_slot].secret, SECRET_MAX, tok)) <= 0)
345 goto fail;
346 }
347 else if(!rb->strcmp(tok, "counter"))
348 {
349 if(accounts[next_slot].is_totp)
350 {
351 rb->splash(HZ * 2, "Counter parameter specified for TOTP!? Skipping...");
352 goto fail;
353 }
354 tok = rb->strtok_r(NULL, "&", &save);
355 accounts[next_slot].hotp_counter = rb->atoi(tok);
356 }
357 else if(!rb->strcmp(tok, "period"))
358 {
359 if(!accounts[next_slot].is_totp)
360 {
361 rb->splash(HZ * 2, "Period parameter specified for HOTP!? Skipping...");
362 goto fail;
363 }
364 tok = rb->strtok_r(NULL, "&", &save);
365 accounts[next_slot].totp_period = rb->atoi(tok);
366 }
367 else if(!rb->strcmp(tok, "digits"))
368 {
369 tok = rb->strtok_r(NULL, "&", &save);
370 accounts[next_slot].digits = rb->atoi(tok);
371 if(accounts[next_slot].digits < 1 || accounts[next_slot].digits > 9)
372 {
373 rb->splashf(HZ * 2, "Digits parameter not in acceptable range, skipping.");
374 goto fail;
375 }
376 }
377 else
378 rb->splashf(HZ, "Unnown parameter `%s' ignored.", tok);
379 } while(tok);
380
381 if(!have_secret)
382 {
383 rb->splashf(HZ * 2, "URI with NO `secret' parameter found, skipping!");
384 goto fail;
385 }
386
387 ++next_slot;
388
389 fail:
390
391 ;
392 } while(1);
393 rb->close(fd);
394 }
395 if(before == next_slot)
396 rb->splash(HZ * 2, "No accounts added.");
397 else
398 {
399 rb->splashf(HZ * 2, "Added %d account(s).", next_slot - before);
400 save_accts();
401 }
402}
403
404static void add_acct_manual(void)
405{
406 if(next_slot >= max_accts)
407 {
408 rb->splashf(HZ * 2, "Account limit reached!");
409 return;
410 }
411 memset(accounts + next_slot, 0, sizeof(struct account_t));
412
413 rb->splash(HZ * 1, "Enter account name.");
414 if(rb->kbd_input(accounts[next_slot].name, sizeof(accounts[next_slot].name)) < 0)
415 return;
416
417 if(acct_exists(accounts[next_slot].name))
418 {
419 rb->splash(HZ * 2, "Duplicate account name!");
420 return;
421 }
422
423 rb->splash(HZ * 2, "Enter base32-encoded secret.");
424
425 char temp_buf[SECRET_MAX * 2];
426 memset(temp_buf, 0, sizeof(temp_buf));
427
428 if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0)
429 return;
430
431 if((accounts[next_slot].sec_len = base32_decode(accounts[next_slot].secret, SECRET_MAX, temp_buf)) <= 0)
432 {
433 rb->splash(HZ * 2, "Invalid Base32 secret!");
434 return;
435 }
436
437#if CONFIG_RTC
438 const struct text_message prompt = { (const char*[]) {"Is this a TOTP account?", "The protocol can be determined from the URI."}, 2};
439 enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
440 if(response == YESNO_NO)
441 accounts[next_slot].is_totp = false;
442 else
443 accounts[next_slot].is_totp = true;
444#endif
445
446 memset(temp_buf, 0, sizeof(temp_buf));
447
448 if(!accounts[next_slot].is_totp)
449 {
450 rb->splash(HZ * 2, "Enter counter (0 is normal).");
451 temp_buf[0] = '0';
452 }
453 else
454 {
455 rb->splash(HZ * 2, "Enter time step (30 is normal).");
456 temp_buf[0] = '3';
457 temp_buf[1] = '0';
458 }
459
460 if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0)
461 return;
462
463 if(!accounts[next_slot].is_totp)
464 accounts[next_slot].hotp_counter = rb->atoi(temp_buf);
465 else
466 accounts[next_slot].totp_period = rb->atoi(temp_buf);
467
468 rb->splash(HZ * 2, "Enter code length (6 is normal).");
469
470 memset(temp_buf, 0, sizeof(temp_buf));
471 temp_buf[0] = '6';
472
473 if(rb->kbd_input(temp_buf, sizeof(temp_buf)) < 0)
474 return;
475
476 accounts[next_slot].digits = rb->atoi(temp_buf);
477
478 if(accounts[next_slot].digits < 1 || accounts[next_slot].digits > 9)
479 {
480 rb->splash(HZ, "Invalid length!");
481 return;
482 }
483
484 ++next_slot;
485
486 save_accts();
487
488 rb->splashf(HZ * 2, "Success.");
489}
490
491static void add_acct(void)
492{
493 MENUITEM_STRINGLIST(menu, "Add Account", NULL,
494 "From URI on disk",
495 "Manual Entry",
496 "Back");
497 int sel = 0;
498 bool quit = false;
499 while(!quit)
500 {
501 switch(rb->do_menu(&menu, &sel, NULL, false))
502 {
503 case 0:
504 add_acct_file();
505 break;
506 case 1:
507 add_acct_manual();
508 break;
509 case 2:
510 default:
511 quit = true;
512 break;
513 }
514 }
515}
516
517static void show_code(int acct)
518{
519 /* rockbox's printf doesn't support a variable field width afaik */
520 char format_buf[64];
521 if(!accounts[acct].is_totp)
522 {
523 rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd", accounts[acct].digits);
524 rb->splashf(0, format_buf, HOTP(accounts[acct].secret,
525 accounts[acct].sec_len,
526 accounts[acct].hotp_counter,
527 accounts[acct].digits));
528 ++accounts[acct].hotp_counter;
529 }
530#if CONFIG_RTC
531 else
532 {
533 rb->snprintf(format_buf, sizeof(format_buf), "%%0%dd (%%ld second(s) left)", accounts[acct].digits);
534 rb->splashf(0, format_buf, TOTP(accounts[acct].secret,
535 accounts[acct].sec_len,
536 accounts[acct].totp_period,
537 accounts[acct].digits),
538 accounts[acct].totp_period - get_utc() % accounts[acct].totp_period);
539 }
540#else
541 else
542 {
543 rb->splash(0, "TOTP not supported on this device!");
544 }
545#endif
546 rb->sleep(HZ * 2);
547 while(1)
548 {
549 int button = rb->button_get(true);
550 if(button && !(button & BUTTON_REL))
551 break;
552 rb->yield();
553 }
554
555 save_accts();
556 rb->lcd_clear_display();
557}
558
559static void gen_codes(void)
560{
561 rb->lcd_clear_display();
562 /* native menus don't seem to support dynamic names easily, so we
563 * roll our own */
564 static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
565 int idx = 0;
566 if(next_slot > 0)
567 {
568 rb->lcd_putsf(0, 0, "Generate Code");
569 rb->lcd_putsf(0, 1, "%s", accounts[0].name);
570 rb->lcd_update();
571 }
572 else
573 {
574 rb->splash(HZ * 2, "No accounts configured!");
575 return;
576 }
577 while(1)
578 {
579 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
580 switch(button)
581 {
582 case PLA_LEFT:
583 --idx;
584 if(idx < 0)
585 idx = next_slot - 1;
586 break;
587 case PLA_RIGHT:
588 ++idx;
589 if(idx >= next_slot)
590 idx = 0;
591 break;
592 case PLA_SELECT:
593 show_code(idx);
594 break;
595 case PLA_CANCEL:
596 case PLA_EXIT:
597 exit_on_usb(button);
598 return;
599 default:
600 break;
601 }
602 rb->lcd_clear_display();
603 rb->lcd_putsf(0, 0, "Generate Code");
604 rb->lcd_putsf(0, 1, "%s", accounts[idx].name);
605 rb->lcd_update();
606 }
607}
608
609static bool danger_confirm(void)
610{
611 int sel = 0;
612 MENUITEM_STRINGLIST(menu, "Are you REALLY SURE?", NULL,
613 "No",
614 "No",
615 "No",
616 "No",
617 "No",
618 "No",
619 "No",
620 "Yes, DO IT", // 7
621 "No",
622 "No",
623 "No",
624 "No");
625
626 switch(rb->do_menu(&menu, &sel, NULL, false))
627 {
628 case 7:
629 return true;
630 default:
631 return false;
632 }
633}
634
635char data_buf[MAX(MAX_NAME, SECRET_MAX * 2)];
636char temp_sec[SECRET_MAX];
637size_t old_len;
638
639static void edit_menu(int acct)
640{
641 rb->splashf(HZ, "Editing account `%s'.", accounts[acct].name);
642
643 /* HACK ALERT */
644 /* two different menus, one handling logic */
645 MENUITEM_STRINGLIST(menu_1, "Edit Account", NULL,
646 "Rename",
647 "Delete",
648 "Change HOTP Counter",
649 "Change Digit Count",
650 "Change Shared Secret",
651 "Back");
652
653 MENUITEM_STRINGLIST(menu_2, "Edit Account", NULL,
654 "Rename", // 0
655 "Delete", // 1
656 "Change TOTP Period", // 2
657 "Change Digit Count", // 3
658 "Change Shared Secret", // 4
659 "Back"); // 5
660
661 const struct menu_item_ex *menu = (accounts[acct].is_totp) ? &menu_2 : &menu_1;
662
663 bool quit = false;
664 int sel = 0;
665 while(!quit)
666 {
667 switch(rb->do_menu(menu, &sel, NULL, false))
668 {
669 case 0: // rename
670 rb->splash(HZ, "Enter new name.");
671 rb->strlcpy(data_buf, accounts[acct].name, sizeof(data_buf));
672 if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0)
673 break;
674 if(acct_exists(data_buf))
675 {
676 rb->splash(HZ * 2, "Duplicate account name!");
677 break;
678 }
679 rb->strlcpy(accounts[acct].name, data_buf, sizeof(accounts[acct].name));
680 save_accts();
681 break;
682 case 1: // delete
683 if(danger_confirm())
684 {
685 rb->memmove(accounts + acct, accounts + acct + 1, (next_slot - acct - 1) * sizeof(struct account_t));
686 --next_slot;
687 save_accts();
688 rb->splashf(HZ, "Deleted.");
689 return;
690 }
691 else
692 rb->splash(HZ, "Not confirmed.");
693 break;
694 case 2: // HOTP counter OR TOTP period
695 if(accounts[acct].is_totp)
696 rb->snprintf(data_buf, sizeof(data_buf), "%d", (int)accounts[acct].hotp_counter);
697 else
698 rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].totp_period);
699
700 if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0)
701 break;
702
703 if(accounts[acct].is_totp)
704 accounts[acct].totp_period = rb->atoi(data_buf);
705 else
706 accounts[acct].hotp_counter = rb->atoi(data_buf);
707
708 save_accts();
709
710 rb->splash(HZ, "Success.");
711 break;
712 case 3: // digits
713 rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].digits);
714 if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0)
715 break;
716
717 accounts[acct].digits = rb->atoi(data_buf);
718 save_accts();
719
720 rb->splash(HZ, "Success.");
721 break;
722 case 4: // secret
723 old_len = accounts[acct].sec_len;
724 memcpy(temp_sec, accounts[acct].secret, accounts[acct].sec_len);
725 base32_encode(accounts[acct].secret, accounts[acct].sec_len, data_buf, sizeof(data_buf));
726
727 if(rb->kbd_input(data_buf, sizeof(data_buf)) < 0)
728 break;
729
730 int ret = base32_decode(accounts[acct].secret, sizeof(accounts[acct].secret), data_buf);
731 if(ret <= 0)
732 {
733 memcpy(accounts[acct].secret, temp_sec, SECRET_MAX);
734 accounts[acct].sec_len = old_len;
735 rb->splash(HZ * 2, "Invalid Base32 secret!");
736 break;
737 }
738 accounts[acct].sec_len = ret;
739
740 save_accts();
741 rb->splash(HZ, "Success.");
742 break;
743 case 5:
744 quit = true;
745 break;
746 default:
747 break;
748 }
749 }
750}
751
752static void edit_accts(void)
753{
754 rb->lcd_clear_display();
755 /* native menus don't seem to support dynamic names easily, so we
756 * roll our own */
757 static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
758 int idx = 0;
759 if(next_slot > 0)
760 {
761 rb->lcd_putsf(0, 0, "Edit Account");
762 rb->lcd_putsf(0, 1, "%s", accounts[0].name);
763 rb->lcd_update();
764 }
765 else
766 {
767 rb->splash(HZ * 2, "No accounts configured!");
768 return;
769 }
770 while(1)
771 {
772 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
773 switch(button)
774 {
775 case PLA_LEFT:
776 --idx;
777 if(idx < 0)
778 idx = next_slot - 1;
779 break;
780 case PLA_RIGHT:
781 ++idx;
782 if(idx >= next_slot)
783 idx = 0;
784 break;
785 case PLA_SELECT:
786 edit_menu(idx);
787 if(!next_slot)
788 return;
789 if(idx == next_slot)
790 idx = 0;
791 break;
792 case PLA_CANCEL:
793 case PLA_EXIT:
794 return;
795 default:
796 exit_on_usb(button);
797 break;
798 }
799 rb->lcd_clear_display();
800 rb->lcd_putsf(0, 0, "Edit Account");
801 rb->lcd_putsf(0, 1, "%s", accounts[idx].name);
802 rb->lcd_update();
803 }
804}
805
806#if CONFIG_RTC
807
808/* label is like this: [+/-]HH:MM ... */
809static int get_time_seconds(const char *label)
810{
811 if(!rb->strcmp(label, "UTC"))
812 return 0;
813
814 char buf[32];
815
816 /* copy the part after "UTC" */
817 rb->strlcpy(buf, label + 3, sizeof(buf));
818
819 char *save, *tok;
820
821 tok = rb->strtok_r(buf, ":", &save);
822 /* positive or negative: sign left */
823 int hr = rb->atoi(tok);
824
825 tok = rb->strtok_r(NULL, ": ", &save);
826 int min = rb->atoi(tok);
827
828 return 3600 * hr + 60 * min;
829}
830
831/* returns the offset in seconds associated with a time zone */
832static int get_time_offs(void)
833{
834 MENUITEM_STRINGLIST(menu, "Select Time Offset", NULL,
835 "UTC-12:00", // 0
836 "UTC-11:00", // 1
837 "UTC-10:00 (HAST)", // 2
838 "UTC-9:30", // 3
839 "UTC-9:00 (AKST, HADT)", // 4
840 "UTC-8:00 (PST, AKDT)", // 5
841 "UTC-7:00 (MST, PDT)", // 6
842 "UTC-6:00 (CST, MDT)", // 7
843 "UTC-5:00 (EST, CDT)", // 8
844 "UTC-4:00 (AST, EDT)", // 9
845 "UTC-3:30 (NST)", // 10
846 "UTC-3:00 (ADT)", // 11
847 "UTC-2:30 (NDT)", // 12
848 "UTC-2:00", // 13
849 "UTC-1:00", // 14
850 "UTC", // 15
851 "UTC+1:00", // 16
852 "UTC+2:00", // 17
853 "UTC+3:00", // 18
854 "UTC+3:30", // 19
855 "UTC+4:00", // 20
856 "UTC+4:30", // 21
857 "UTC+5:00", // 22
858 "UTC+5:30", // 23
859 "UTC+5:45", // 24
860 "UTC+6:00", // 25
861 "UTC+6:30", // 26
862 "UTC+7:00", // 27
863 "UTC+8:00", // 28
864 "UTC+8:30", // 29
865 "UTC+8:45", // 30
866 "UTC+9:00", // 31
867 "UTC+9:30", // 32
868 "UTC+10:00", // 33
869 "UTC+10:30", // 34
870 "UTC+11:00", // 35
871 "UTC+12:00", // 36
872 "UTC+12:45", // 37
873 "UTC+13:00", // 38
874 "UTC+14:00", // 39
875 );
876
877 int sel = 0;
878 for(unsigned int i = 0; i < ARRAYLEN(menu_); ++i)
879 if(time_offs == get_time_seconds(menu_[i]))
880 {
881 sel = i;
882 break;
883 }
884
885 /* relies on menu internals */
886 rb->do_menu(&menu, &sel, NULL, false);
887
888 /* see apps/menu.h */
889 const char *label = menu_[sel];
890
891 return get_time_seconds(label);
892
893#if 0
894 /* provided in case menu internals change */
895 switch(rb->do_menu(&menu, &sel, NULL, false))
896 {
897 case 0: case 1: case 2:
898 return (sel - 12) * 3600;
899 case 3:
900 return -9 * 3600 - 30 * 60;
901 case 4: case 5: case 6: case 7: case 8: case 9:
902 return (sel - 13) * 3600;
903 case 10:
904 return -3 * 3600 - 30 * 60;
905 case 11:
906 return -3 * 3600;
907 case 12:
908 return -3 * 3600 - 30 * 60;
909 case 13: case 14: case 15: case 16: case 17: case 18:
910 return (sel - 15) * 3600;
911
912 case 19:
913 return 3 * 3600 + 30 * 60;
914 case 20:
915 return 4 * 3600;
916 case 21:
917 return 4 * 3600 + 30 * 60;
918 case 22:
919 return 5 * 3600;
920 case 23:
921 return 5 * 3600 + 30 * 60;
922 case 24:
923 return 5 * 3600 + 45 * 60;
924 case 25:
925 return 6 * 3600;
926 case 26:
927 return 6 * 3600 + 30 * 60;
928 case 27: case 28:
929 return (sel - 20) * 3600;
930 case 29:
931 return 8 * 3600 + 30 * 60;
932 case 30:
933 return 8 * 3600 + 45 * 60;
934 case 31:
935 return 9 * 3600;
936 case 32:
937 return 9 * 3600 + 30 * 60;
938 case 33:
939 return 10 * 3600;
940 case 34:
941 return 10 * 3600 + 30 * 60;
942 case 35: case 36:
943 return (sel - 24) * 3600;
944 case 37:
945 return 12 * 3600 + 45 * 60;
946 case 38: case 39:
947 return (sel - 25) * 3600;
948 default:
949 rb->splash(0, "BUG: time zone fall-through: REPORT ME!!!");
950 break;
951 }
952 return 0;
953#endif
954}
955#endif
956
957static void adv_menu(void)
958{
959 MENUITEM_STRINGLIST(menu, "Advanced", NULL,
960 "Edit Account",
961 "Delete ALL accounts",
962#if CONFIG_RTC
963 "Change Time Offset",
964#endif
965 "Back");
966
967 bool quit = false;
968 int sel = 0;
969 while(!quit)
970 {
971 switch(rb->do_menu(&menu, &sel, NULL, false))
972 {
973 case 0:
974 edit_accts();
975 break;
976 case 1:
977 if(danger_confirm())
978 {
979 next_slot = 0;
980 save_accts();
981 rb->splash(HZ, "It is done, my master.");
982 }
983 else
984 rb->splash(HZ, "Not confirmed.");
985 break;
986#if CONFIG_RTC
987 case 2:
988 time_offs = get_time_offs();
989 break;
990 case 3:
991#else
992 case 2:
993#endif
994 quit = 1;
995 break;
996 default:
997 break;
998 }
999 }
1000}
1001
1002/* displays the help text */
1003static void show_help(void)
1004{
1005
1006#ifdef HAVE_LCD_COLOR
1007 rb->lcd_set_foreground(LCD_WHITE);
1008 rb->lcd_set_background(LCD_BLACK);
1009#endif
1010
1011#ifdef HAVE_LCD_BITMAP
1012 rb->lcd_setfont(FONT_UI);
1013#endif
1014
1015 static char *help_text[] = { "One-Time Password Manager", "",
1016 "Introduction", "",
1017 "This", "plugin", "allows", "you", "to", "generate", "one-time", "passwords", "to", "provide", "a", "second", "factor", "of", "authentication", "for", "services", "that", "support", "it.",
1018 "It", "suppports", "both", "event-based", "(HOTP),", "and", "time-based", "(TOTP)", "password", "schemes.",
1019 "In", "order", "to", "ensure", "proper", "functioning", "of", "time-based", "passwords", "ensure", "that", "the", "clock", "is", "accurate", "to", "within", "30", "seconds", "of", "actual", "time."
1020 "Note", "that", "some", "devices", "lack", "a", "real-time", "clock,", "so", "time-based", "passwords", "are", "not", "supported", "on", "those", "targets." };
1021
1022 struct style_text style[] = {
1023 {0, TEXT_CENTER | TEXT_UNDERLINE},
1024 {2, C_RED},
1025 LAST_STYLE_ITEM
1026 };
1027
1028 display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
1029}
1030
1031/* this is the plugin entry point */
1032enum plugin_status plugin_start(const void* parameter)
1033{
1034 (void)parameter;
1035
1036 /* self-test with RFC 4226 values */
1037 if(HOTP("12345678901234567890", rb->strlen("12345678901234567890"), 1, 6) != 287082)
1038 {
1039 return PLUGIN_ERROR;
1040 }
1041
1042 size_t bufsz;
1043 accounts = rb->plugin_get_buffer(&bufsz);
1044 max_accts = bufsz / sizeof(struct account_t);
1045
1046 if(!read_accts())
1047#if CONFIG_RTC
1048 {
1049 time_offs = get_time_offs();
1050 }
1051#else
1052 {
1053 ;
1054 }
1055#endif
1056
1057 MENUITEM_STRINGLIST(menu, "One-Time Password Manager", NULL,
1058 "Add Account",
1059 "Generate Code",
1060 "Help",
1061 "Advanced",
1062 "Quit");
1063
1064 bool quit = false;
1065 int sel = 0;
1066 while(!quit)
1067 {
1068 switch(rb->do_menu(&menu, &sel, NULL, false))
1069 {
1070 case 0:
1071 add_acct();
1072 break;
1073 case 1:
1074 gen_codes();
1075 break;
1076 case 2:
1077 show_help();
1078 break;
1079 case 3:
1080 adv_menu();
1081 break;
1082 case 4:
1083 quit = 1;
1084 break;
1085 default:
1086 break;
1087 }
1088 }
1089
1090 /* save to disk */
1091 save_accts();
1092
1093 /* tell Rockbox that we have completed successfully */
1094 return PLUGIN_OK;
1095}
diff --git a/manual/configure_rockbox/time_and_date.tex b/manual/configure_rockbox/time_and_date.tex
index fe1624211b..aa3f563eed 100644
--- a/manual/configure_rockbox/time_and_date.tex
+++ b/manual/configure_rockbox/time_and_date.tex
@@ -1,5 +1,7 @@
1% $Id:$ % 1% $Id:$ %
2 2
3\label{ref:Timeanddateactual}
4
3Time related menu options. Pressing \ActionStdContext{} will voice the current time 5Time related menu options. Pressing \ActionStdContext{} will voice the current time
4if voice support is enabled. 6if voice support is enabled.
5 7
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex
index e5f5deb140..2cd49035ff 100644
--- a/manual/plugins/main.tex
+++ b/manual/plugins/main.tex
@@ -272,6 +272,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).}
272 272
273{\input{plugins/metronome.tex}} 273{\input{plugins/metronome.tex}}
274 274
275{\input{plugins/otp.tex}}
276
275\opt{lcd_bitmap}{\input{plugins/periodic_table.tex}} 277\opt{lcd_bitmap}{\input{plugins/periodic_table.tex}}
276 278
277\opt{swcodec}{\opt{recording_mic}{\input{plugins/pitch_detector.tex}}} 279\opt{swcodec}{\opt{recording_mic}{\input{plugins/pitch_detector.tex}}}
diff --git a/manual/plugins/otp.tex b/manual/plugins/otp.tex
new file mode 100644
index 0000000000..5b1a29f8c2
--- /dev/null
+++ b/manual/plugins/otp.tex
@@ -0,0 +1,72 @@
1% $Id$ %
2\subsection{One-Time Password Client}
3This plugin provides the ability to generate one-time passwords (OTPs)
4for authentication purposes. It implements an HMAC-based One-Time
5Password Algorithm (RFC 4226), and on targets which support it, a
6Time-based One-Time Password Algorithm (RFC 6238).
7
8\subsubsection{Adding Accounts}
9The plugin supports two methods of adding accounts: URI import, and
10manual entry.
11
12\opt{rtc}{ It is important to note that for TOTP (time-based) accounts
13 to work properly, the clock on your device MUST be accurate to no
14 less than 30 seconds from the time on the authentication server, and
15 the correct time zone must be configured in the plugin. See
16 \reference{ref:Timeanddateactual} for more information. }
17
18\subsubsection{URI Import}
19This method of adding an account reads a list of URIs from a file. It
20expects each URI to be on a line by itself in the following format:
21
22\begin{verbatim}
23otpauth://[hotp OR totp]/[account name]?secret=[Base32 secret][&counter=X][&period=X][&digits=X]
24\end{verbatim}
25
26An example is shown below, provisioning a TOTP key for an account called ``bob'':
27
28\begin{verbatim}
29otpauth://totp/bob?secret=JBSWY3DPEHPK3PXP
30\end{verbatim}
31
32Any other URI options are not supported and will be ignored.
33
34Most services will provide a scannable QR code that encodes a OTP
35URI. In order to use those, first scan the QR code separately and save
36the URI to a file on your device. If necessary, rewrite the URI so it
37is in the format shown above. For example, GitHub's URI has a slash
38after the provider. In order for this URI to be properly parsed, you
39must rewrite the account name so that it does not contain a slash.
40
41\subsubsection{Manual Import}
42If direct URI import is not possible, the plugin supports the manual
43entry of data associated with an account. After you select the
44``Manual Entry'' option, it will prompt you for an account name. You
45may type anything you wish, but it should be memorable. It will then
46prompt you for the Base32-encoded secret. Most services will provide
47this to you directly, but some may only provide you with a QR code. In
48these cases, you must scan the QR code separately, and then enter the
49string following the ``secret='' parameter on your Rockbox device
50manually.
51
52On devices with a real-time clock, \opt{rtc}{like yours,} the plugin
53will ask whether the account is a time-based account
54(TOTP). \opt{rtc}{If you answer ``yes'' to this question, it will ask
55 for further information regarding the account. Usually it is safe to
56 accept the defaults here. } However, if your device lacks a
57real-time clock, the plugin's functionality will be restricted to
58HMAC-based (HOTP) accounts only. If this is the case, the plugin will
59prompt you for information regarding the HOTP setup.
60
61\opt{rtc} {
62 \subsection{Advanced Settings}
63 \subsubsection{Time Zone Configuration}
64 In order for TOTP accounts to work properly, the plugin must be able
65 to determine the current UTC time. This means that, first, your
66 device's clock must be synchronized with UTC time, and second, that
67 the plugin knows what time zone the clock is using. The plugin will
68 prompt you on its first run for this piece of information. However,
69 should this setting need changing at a later time, possibly due to
70 Daylight Saving Time adjustment, it is located under the
71 ``Advanced'' submenu. NOTE: in the UI simulator, use the ``UTC''
72 setting no matter what the clock may read. }