A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

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

+1724
+1
apps/plugins/CATEGORIES
··· 65 65 mpegplayer,viewers 66 66 nim,games 67 67 oscilloscope,demos 68 + otp,apps 68 69 pacbox,games 69 70 pdbox,viewers 70 71 pegbox,games
+1
apps/plugins/SOURCES
··· 33 33 flipit.c 34 34 shopper.c 35 35 resistor.c 36 + otp.c 36 37 37 38 #ifdef USB_ENABLE_HID 38 39 remote_control.c
+1
apps/plugins/lib/SOURCES
··· 1 + sha1.c 1 2 gcc-support.c 2 3 pluginlib_actions.c 3 4 helper.c
+434
apps/plugins/lib/sha1.c
··· 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) */ 43 + static 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. */ 49 + void 50 + sha1_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. */ 67 + void * 68 + sha1_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. */ 84 + void * 85 + sha1_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. */ 112 + void *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 + 126 + void 127 + sha1_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 + 199 + void 200 + sha1_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 + 351 + void * 352 + memxor (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 + 385 + int 386 + hmac_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 + }
+116
apps/plugins/lib/sha1.h
··· 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. */ 26 + struct 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. */ 41 + void 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!!! */ 47 + void 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. */ 54 + void 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. */ 64 + void *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. */ 73 + void *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. */ 79 + void *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. */ 112 + int 113 + hmac_sha1 (const void *key, size_t keylen, 114 + const void *in, size_t inlen, void *resbuf); 115 + 116 + #endif /* HMAC_H */
+1095
apps/plugins/otp.c
··· 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 + 40 + struct 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 + 56 + static int max_accts = 0; 57 + 58 + /* in plugin buffer */ 59 + static struct account_t *accounts = NULL; 60 + 61 + static int next_slot = 0; 62 + 63 + /* in SECONDS, asked for on first run */ 64 + static int time_offs = 0; 65 + 66 + static 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 92 + static time_t get_utc(void) 93 + { 94 + return rb->mktime(rb->get_time()) - time_offs; 95 + } 96 + 97 + static 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 */ 105 + static 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 + 130 + static 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 + 172 + static 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 + 209 + static 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 + 225 + static 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 + 254 + static 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 + 266 + static 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 + 404 + static 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 + 491 + static 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 + 517 + static 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 + 559 + static 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 + 609 + static 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 + 635 + char data_buf[MAX(MAX_NAME, SECRET_MAX * 2)]; 636 + char temp_sec[SECRET_MAX]; 637 + size_t old_len; 638 + 639 + static 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 + 752 + static 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 ... */ 809 + static 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 */ 832 + static 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 + 957 + static 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 */ 1003 + static 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 */ 1032 + enum 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 + }
+2
manual/configure_rockbox/time_and_date.tex
··· 1 1 % $Id:$ % 2 2 3 + \label{ref:Timeanddateactual} 4 + 3 5 Time related menu options. Pressing \ActionStdContext{} will voice the current time 4 6 if voice support is enabled. 5 7
+2
manual/plugins/main.tex
··· 272 272 273 273 {\input{plugins/metronome.tex}} 274 274 275 + {\input{plugins/otp.tex}} 276 + 275 277 \opt{lcd_bitmap}{\input{plugins/periodic_table.tex}} 276 278 277 279 \opt{swcodec}{\opt{recording_mic}{\input{plugins/pitch_detector.tex}}}
+72
manual/plugins/otp.tex
··· 1 + % $Id$ % 2 + \subsection{One-Time Password Client} 3 + This plugin provides the ability to generate one-time passwords (OTPs) 4 + for authentication purposes. It implements an HMAC-based One-Time 5 + Password Algorithm (RFC 4226), and on targets which support it, a 6 + Time-based One-Time Password Algorithm (RFC 6238). 7 + 8 + \subsubsection{Adding Accounts} 9 + The plugin supports two methods of adding accounts: URI import, and 10 + manual 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} 19 + This method of adding an account reads a list of URIs from a file. It 20 + expects each URI to be on a line by itself in the following format: 21 + 22 + \begin{verbatim} 23 + otpauth://[hotp OR totp]/[account name]?secret=[Base32 secret][&counter=X][&period=X][&digits=X] 24 + \end{verbatim} 25 + 26 + An example is shown below, provisioning a TOTP key for an account called ``bob'': 27 + 28 + \begin{verbatim} 29 + otpauth://totp/bob?secret=JBSWY3DPEHPK3PXP 30 + \end{verbatim} 31 + 32 + Any other URI options are not supported and will be ignored. 33 + 34 + Most services will provide a scannable QR code that encodes a OTP 35 + URI. In order to use those, first scan the QR code separately and save 36 + the URI to a file on your device. If necessary, rewrite the URI so it 37 + is in the format shown above. For example, GitHub's URI has a slash 38 + after the provider. In order for this URI to be properly parsed, you 39 + must rewrite the account name so that it does not contain a slash. 40 + 41 + \subsubsection{Manual Import} 42 + If direct URI import is not possible, the plugin supports the manual 43 + entry of data associated with an account. After you select the 44 + ``Manual Entry'' option, it will prompt you for an account name. You 45 + may type anything you wish, but it should be memorable. It will then 46 + prompt you for the Base32-encoded secret. Most services will provide 47 + this to you directly, but some may only provide you with a QR code. In 48 + these cases, you must scan the QR code separately, and then enter the 49 + string following the ``secret='' parameter on your Rockbox device 50 + manually. 51 + 52 + On devices with a real-time clock, \opt{rtc}{like yours,} the plugin 53 + will 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 57 + real-time clock, the plugin's functionality will be restricted to 58 + HMAC-based (HOTP) accounts only. If this is the case, the plugin will 59 + prompt 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. }