diff --git a/logic/menu.c b/logic/menu.c index 4e8e240..d3b53f3 100644 --- a/logic/menu.c +++ b/logic/menu.c @@ -96,6 +96,11 @@ #include "gps.h" #endif +#ifdef CONFIG_OTP +#include "otp.h" +#endif + + // ************************************************************************************************* // Defines section @@ -442,6 +447,19 @@ const struct menu menu_L2_Gps = }; #endif +#ifdef CONFIG_OTP +// OTP +const struct menu menu_L2_Otp = +{ + FUNCTION(otp_sx), // direct function + FUNCTION(otp_switch), // sub menu function + FUNCTION(menu_skip_next), // next item function + FUNCTION(display_otp), // display function + FUNCTION(update_otp), // new display data +}; +#endif + + // ************************************************************************************************* // menu array @@ -488,6 +506,9 @@ const struct menu *menu_L2[]={ #ifdef CONFIG_BATTERY &menu_L2_Battery, #endif + #ifdef CONFIG_OTP + &menu_L2_Otp, + #endif #ifdef CONFIG_PHASE_CLOCK &menu_L2_Phase, #endif diff --git a/logic/menu.h b/logic/menu.h index cc4090a..c92b63d 100644 --- a/logic/menu.h +++ b/logic/menu.h @@ -131,6 +131,11 @@ extern const struct menu menu_L2_Vario; extern const struct menu menu_L2_Gps; #endif +#ifdef CONFIG_USE_OTP +extern const struct menu menu_L2_Otp; +#endif + + // Pointers to current menu item extern const struct menu * ptrMenu_L1; extern const struct menu * ptrMenu_L2; diff --git a/logic/otp.c b/logic/otp.c new file mode 100644 index 0000000..6e6737a --- /dev/null +++ b/logic/otp.c @@ -0,0 +1,472 @@ +/** + Copyright (c) 2011 Yohanes Nugroho (yohanes@gmail.com) + Copyright (c) 2011 Google Inc. (qwandor@google.com) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + The SHA1 code is based on public domain code by + Uwe Hollerbach + from Peter C. Gutmann's implementation as found in + Applied Cryptography by Bruce Schneier +*/ +#include "project.h" + +#ifdef CONFIG_OTP + +#include + +#include "ports.h" +#include "display.h" +#include "timer.h" +#include "buzzer.h" +#include "user.h" +#include "clock.h" +#include "date.h" + +// logic +#include "menu.h" + +#include "otp.h" + +#undef C + +extern struct date sDate; + +#define SHA1_BLOCKSIZE 64 +#define SHA1_DIGEST_LENGTH 20 + +/*in this implementation: MAX = 63*/ +#define HMAC_KEY_LENGTH (sizeof(CONFIG_OTP_KEY) - 1) +#define HMAC_DATA_LENGTH 8 + +#ifdef CONFIG_HOTP +#define HOTP +#else +#define TOTP +#endif + +static uint8_t hmac_key[HMAC_KEY_LENGTH]; +static uint32_t sha1_digest[8]; +static uint32_t sha1_count; +static uint8_t sha1_data[SHA1_BLOCKSIZE]; +static uint32_t sha1_W[80]; +static uint8_t hmac_tmp_key[64 + SHA1_DIGEST_LENGTH]; // 64 + max(HMAC_DATA_LENGTH, SHA1_DIGEST_LENGTH) +static uint8_t hmac_sha[SHA1_DIGEST_LENGTH]; + +// The key for the inner digest is derived from our key, by padding the key +// the full length of 64 bytes, and then XOR'ing each byte with 0x36. + + +/* SHA f()-functions */ +#define f1(x,y,z) ((x & y) | (~x & z)) +#define f2(x,y,z) (x ^ y ^ z) +#define f3(x,y,z) ((x & y) | (x & z) | (y & z)) +#define f4(x,y,z) (x ^ y ^ z) + +/* SHA constants */ +#define CONST1 0x5a827999L +#define CONST2 0x6ed9eba1L +#define CONST3 0x8f1bbcdcL +#define CONST4 0xca62c1d6L + +/* truncate to 32 bits -- should be a null op on 32-bit machines */ +#define T32(x) ((x) & 0xffffffffL) + +#define R32(x,n) T32(((x << n) | (x >> (32 - n)))) + +/* the generic case, for when the overall rotation is not unraveled */ +#define FG(n) \ + T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); \ + E = D; D = C; C = R32(B,30); B = A; A = T + + +void sha1_transform() +{ + int i; + uint8_t *dp; + uint32_t T, A, B, C, D, E, *WP; + + dp = sha1_data; + +#define SWAP_DONE + for (i = 0; i < 16; ++i) { + T = *((uint32_t *) dp); + dp += 4; + sha1_W[i] = + ((T << 24) & 0xff000000) | + ((T << 8) & 0x00ff0000) | + ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff); + } + + for (i = 16; i < 80; ++i) { + sha1_W[i] = sha1_W[i-3] ^ sha1_W[i-8] ^ sha1_W[i-14] ^ sha1_W[i-16]; + sha1_W[i] = R32(sha1_W[i], 1); + } + + + A = sha1_digest[0]; + B = sha1_digest[1]; + C = sha1_digest[2]; + D = sha1_digest[3]; + E = sha1_digest[4]; + WP = sha1_W; + for (i = 0; i < 20; ++i) { FG(1); } + for (i = 20; i < 40; ++i) { FG(2); } + for (i = 40; i < 60; ++i) { FG(3); } + for (i = 60; i < 80; ++i) { FG(4); } + + sha1_digest[0] = T32(sha1_digest[0] + A); + sha1_digest[1] = T32(sha1_digest[1] + B); + sha1_digest[2] = T32(sha1_digest[2] + C); + sha1_digest[3] = T32(sha1_digest[3] + D); + sha1_digest[4] = T32(sha1_digest[4] + E); + +} + +void sha1(const uint8_t* data, uint32_t len, uint8_t digest[20]) +{ + + int i; + + int count; + uint32_t lo_bit_count; + + + sha1_digest[0] = 0x67452301L; + sha1_digest[1] = 0xefcdab89L; + sha1_digest[2] = 0x98badcfeL; + sha1_digest[3] = 0x10325476L; + sha1_digest[4] = 0xc3d2e1f0L; + sha1_count = 0L; + + sha1_count = T32(((uint32_t) len << 3)); + + while (len >= SHA1_BLOCKSIZE) { + memcpy(sha1_data, data, SHA1_BLOCKSIZE); + data += SHA1_BLOCKSIZE; + len -= SHA1_BLOCKSIZE; + sha1_transform(); + } + memcpy(sha1_data, data, len); + + lo_bit_count = sha1_count; + + count = (int) ((lo_bit_count >> 3) & 0x3f); + ((uint8_t *) sha1_data)[count++] = 0x80; + if (count > SHA1_BLOCKSIZE - 8) { + memset(((uint8_t *) sha1_data) + count, 0, SHA1_BLOCKSIZE - count); + sha1_transform(); + memset((uint8_t *) sha1_data, 0, SHA1_BLOCKSIZE - 8); + } else { + memset(((uint8_t *) sha1_data) + count, 0, + SHA1_BLOCKSIZE - 8 - count); + } + + sha1_data[56] = 0; + sha1_data[57] = 0; + sha1_data[58] = 0; + sha1_data[59] = 0; + sha1_data[60] = (uint8_t)((lo_bit_count >> 24) & 0xff); + sha1_data[61] = (uint8_t)((lo_bit_count >> 16) & 0xff); + sha1_data[62] = (uint8_t)((lo_bit_count >> 8) & 0xff); + sha1_data[63] = (uint8_t)((lo_bit_count >> 0) & 0xff); + + sha1_transform(); + + //memcpy(digest, sha1_digest, 20); + + count = 0; + for(i = 0; i<5; i++) { + digest[count++] = (unsigned char) ((sha1_digest[i] >> 24) & 0xff); + digest[count++] = (unsigned char) ((sha1_digest[i] >> 16) & 0xff); + digest[count++] = (unsigned char) ((sha1_digest[i] >> 8) & 0xff); + digest[count++] = (unsigned char) ((sha1_digest[i]) & 0xff); + } + +} + + +//data is in tmp_key + 64 +//result is in hmac_sha +uint8_t* hmac_sha1(uint8_t *data) { + + int i; + + + // The key for the inner digest is derived from our key, by padding the key + // the full length of 64 bytes, and then XOR'ing each byte with 0x36. + + for (i = 0; i < HMAC_KEY_LENGTH; ++i) { + hmac_tmp_key[i] = hmac_key[i] ^ 0x36; + } + memset(hmac_tmp_key + HMAC_KEY_LENGTH, 0x36, 64 - HMAC_KEY_LENGTH); + + memcpy(hmac_tmp_key + 64, data, HMAC_DATA_LENGTH); + + sha1(hmac_tmp_key, 64 + HMAC_DATA_LENGTH, hmac_sha); + + + // The key for the outer digest is derived from our key, by padding the key + // the full length of 64 bytes, and then XOR'ing each byte with 0x5C. + for (i = 0; i < HMAC_KEY_LENGTH; ++i) { + hmac_tmp_key[i] = hmac_key[i] ^ 0x5C; + } + memset(hmac_tmp_key + HMAC_KEY_LENGTH, 0x5C, 64 - HMAC_KEY_LENGTH); + + memcpy(hmac_tmp_key + 64, hmac_sha, SHA1_DIGEST_LENGTH); + + sha1(hmac_tmp_key, 64 + SHA1_DIGEST_LENGTH, hmac_sha); + + + return hmac_sha; +} + +#ifdef TOTP +static int days[12] ={0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + +uint32_t simple_mktime(int year, int month, int day, int hour, int minute, int second) +{ + uint32_t result; + + year += month / 12; + month %= 12; + result = (year - 1970) * 365 + days[month]; + if (month <= 1) + year -= 1; + + //only works for year 2000 - 2032 + + result += (year - 1968) / 4; + + result += day -1; + + result = ((result*24+hour)*60+minute)*60 + second; + + return (result); +} +#endif + +static uint8_t data[] = {0,0,0,0,0,0,0,0}; +static uint32_t last_val = 0; + +const char *key = CONFIG_OTP_KEY; + +#ifdef TOTP +extern struct date sDate; +extern struct time sTime; + +static uint32_t last_time = 0; +#endif + +#ifdef HOTP +static uint64_t counter = 0; +#endif + +#ifdef TOTP +uint32_t otp() +{ + uint32_t val = 0; + int i; + + uint32_t time = simple_mktime(sDate.year, sDate.month - 1, sDate.day, + sTime.hour, sTime.minute, sTime.second); + + time -= CONFIG_OTP_UTC_OFFSET*3600; + + time /= 30; + + if (time==last_time) { + return last_val; + } + + memcpy(hmac_key, key, HMAC_KEY_LENGTH); + + last_time = time; + + data[4] = (time >> 24) & 0xff; + data[5] = (time >> 16) & 0xff; + data[6] = (time >> 8) & 0xff; + data[7] = (time) & 0xff; + + hmac_sha1(data); + + int off = hmac_sha[SHA1_DIGEST_LENGTH-1] & 0x0f; + + char *cc = (char *)&val; + for (i = 0; i < 4; i++) { + cc[3-i] = hmac_sha[off+i]; + } + val &= 0x7fffffff; + val %= 1000000; + + last_val = val; + + return val; +} +#endif + +#ifdef HOTP +uint32_t otp() +{ + int i; + uint64_t temp = counter; + for (i = 0; i < 8; ++i) { + data[7 - i] = temp & 0xff; + temp >>= 8; + } + + memcpy(hmac_key, key, HMAC_KEY_LENGTH); + hmac_sha1(data); + + int off = hmac_sha[SHA1_DIGEST_LENGTH - 1] & 0x0f; + + last_val = 0; + char *cc = (char *) &last_val; + for (i = 0; i < 4; i++) { + cc[3 - i] = hmac_sha[off + i]; + } + last_val &= 0x7fffffff; + last_val %= 1000000; + + ++counter; + + return last_val; +} +#endif + +static int display_mode = 0; //show first 2 digits + +void otp_sx(u8 line) +{ + display_mode = !display_mode; + display_otp(line, DISPLAY_LINE_UPDATE_PARTIAL); +} + +void otp_switch(u8 line) +{ + otp(); +#ifdef HOTP + display_mode = 0; +#endif + display_otp(line, DISPLAY_LINE_UPDATE_PARTIAL); +} + +u8 update_otp(u8 line, u8 update) +{ +#ifdef TOTP + otp(); +#endif + return 0; +} + +#ifdef TEST_SHA1 + +static char *test_data[] = { + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "A million repetitions of 'a'"}; +static char *test_results[] = { + "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D", + "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1", + "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"}; + +static void hex_p(char *s, int c) +{ + const char *hextab = "0123456789ABCDEF"; + s[0] = hextab[(c >> 4) & 0xf]; + s[1] = hextab[c & 0xf]; +} + + +void digest_to_hex(const uint8_t digest[SHA1_DIGEST_LENGTH], char *output) +{ + int i,j; + char *c = output; + + for (i = 0; i < SHA1_DIGEST_LENGTH/4; i++) { + for (j = 0; j < 4; j++) { + hex_p(c, digest[i*4+j]); + c += 2; + } + *c = ' '; + c += 1; + } + *(c - 1) = '\0'; +} + +int test_result = 0; + +void test_sha1() +{ + int k; + uint8_t digest[20]; + uint8_t output[80]; + + for (k = 0; k < 2; k++){ + + sha1(test_data[k], strlen(test_data[k]), digest); + digest_to_hex(digest, output); + if (strcmp(output, test_results[k])) { + test_result = 1; + break; + } + } + +} + +#endif + +void display_otp(u8 line, u8 update) +{ + +#ifdef TEST_SHA1 + test_sha1(); +#endif + + if (update == DISPLAY_LINE_UPDATE_FULL || update == DISPLAY_LINE_UPDATE_PARTIAL) + { + u8 *str; + + display_symbol(LCD_ICON_HEART, SEG_ON); + +#ifdef TOTP + otp(); +#endif + + if (!display_mode) { + display_symbol(LCD_SYMB_MAX, SEG_OFF); + int v = (last_val / 10000) % 100; + str = _itoa(v, 2, 0); + display_chars(LCD_SEG_L2_1_0, str, SEG_ON); + } else { + display_symbol(LCD_SYMB_MAX, SEG_ON); + int v = (last_val % 10000); + str = _itoa(v, 4, 0); + display_chars(LCD_SEG_L2_3_0, str, SEG_ON); + } + +#ifdef TEST_SHA1 + if (test_result) + str = "9999"; + display_chars(LCD_SEG_L2_3_0, str, SEG_ON); +#endif + } + if (update == DISPLAY_LINE_CLEAR) { + display_symbol(LCD_ICON_HEART, SEG_OFF); + display_symbol(LCD_SYMB_MAX, SEG_OFF); + display_mode = 0; + } +} + +#endif diff --git a/logic/otp.h b/logic/otp.h new file mode 100644 index 0000000..650257c --- /dev/null +++ b/logic/otp.h @@ -0,0 +1,11 @@ +#ifndef OTP_H +#define OTP_H + + +extern void otp_sx(u8 line); +extern void otp_switch(u8 line); +extern void display_otp(u8 line, u8 update); +extern u8 update_otp(u8 line, u8 update); + + +#endif diff --git a/makefile b/makefile index 87b002b..57fdd5e 100644 --- a/makefile +++ b/makefile @@ -21,7 +21,7 @@ CC_INCLUDE = -I$(PROJ_DIR)/ -I$(PROJ_DIR)/include/ -I$(PROJ_DIR)/gcc/ -I$(PROJ_D CC_COPT = $(CC_CMACH) $(CC_DMACH) $(CC_DOPT) $(CC_INCLUDE) LOGIC_SOURCE = logic/acceleration.c logic/alarm.c logic/altitude.c logic/battery.c logic/clock.c logic/date.c logic/menu.c logic/rfbsl.c logic/rfsimpliciti.c logic/stopwatch.c logic/temperature.c logic/test.c logic/user.c logic/phase_clock.c logic/eggtimer.c logic/prout.c logic/vario.c logic/sidereal.c logic/strength.c \ - logic/sequence.c logic/gps.c logic/dst.c + logic/sequence.c logic/gps.c logic/dst.c logic/otp.c LOGIC_O = $(addsuffix .o,$(basename $(LOGIC_SOURCE))) diff --git a/tools/config.py b/tools/config.py index 49930e4..9bc6931 100755 --- a/tools/config.py +++ b/tools/config.py @@ -4,7 +4,7 @@ import urwid import urwid.raw_display import sys - +import base64 import re, sys, random from sorteddict import SortedDict @@ -18,6 +18,16 @@ def rand_hw(): res.sort(reverse=True) return "{" + ",".join([hex(x) for x in res]) + "}" +#cstring is in the form of "\xaa\xbb" +def b32encoded_string_to_c_string(b32key): + key = base64.b32decode(b32key.upper().replace(" ","")) + return '"' + "".join(map(lambda x:"\\x%02x" % ord(x), list(key))) + '"' + +def c_string_to_b32encoded_string(cstring): + cstring = cstring.replace('"', '') + s = "".join(map (lambda x: chr(int("0x" + x, 16)), cstring.split("\\x")[1:])) + return base64.b32encode(s) + DATA = SortedDict() DATA["CONFIG_FREQUENCY"] = { @@ -288,6 +298,36 @@ def rand_hw(): "default": False, "help": "Send time in morse code"} +###Implemented by Yohanes Nugroho (yohanes@gmail.com) + +DATA["CONFIG_OTP"] = { + "name": "OTP Function", + "depends": [], + "default": False, + "help": "Enable Time based OTP (one use of it is for google-authentication)" + } + +DATA["CONFIG_HOTP"] = { + "name": "HOTP algorithm", + "depends": ["CONFIG_OTP"], + "default": False, + "help": "Use event-based OTP rather than time-based"} + +DATA["CONFIG_OTP_KEY"] = { + "name": "OTP Key (in base32 encoded format)", + "depends": ["CONFIG_OTP"], + "default": "", + "type": "text", + "help": "OTP Key in base32 encoded format (spaces will be ignored)" } + +DATA["CONFIG_OTP_UTC_OFFSET"] = { + "name": "Offset from UTC for OTP Key generation", + "depends": ["CONFIG_OTP"], + "default": 0, + "type": "text", + "help": "Offset from UTC in hours (can be negative)" } + + HEADER = """ #ifndef _CONFIG_H_ #define _CONFIG_H_ @@ -375,7 +415,7 @@ def main(self): list_content.append(hgf) elif field["type"] == "text": - f = urwid.AttrWrap(urwid.Edit("%s: "%field["name"], field["value"]), + f = urwid.AttrWrap(urwid.Edit("%s: "%field["name"], str(field["value"])), 'editbx', 'editfc') f._datafield = field self.fields[key] = f @@ -442,7 +482,7 @@ def save_config(self): if item.get_state(): # found the set radio button DATA[key]["value"] = item.value - # look up the + # look up the elif isinstance(field, urwid.Text): pass elif isinstance(field, urwid.AttrMap): @@ -454,6 +494,20 @@ def save_config(self): else: raise ValueError, "Unhandled type" + #special handling for OTP encoding + otp = DATA["CONFIG_OTP"] + otp_key = DATA["CONFIG_OTP_KEY"] + + if (len(otp_key["value"])>0): + try: + otp_key["value"] = b32encoded_string_to_c_string(otp_key["value"]) + except (TypeError) as a: + print "ERROR: invalid OTP Key (" + str(a) + ")" + if (otp["value"]): + print "OTP will be disabled" + otp["value"] = False + otp_key['value'] = '""' + fp = open("config.h", "w") fp.write("// !!!! DO NOT EDIT !!!, use: make config\n") fp.write(HEADER) @@ -510,7 +564,12 @@ def set_default(): m = m.groups() DATA[m[0]]["value"] = False - set_default() + otp_key = DATA["CONFIG_OTP_KEY"] + + if ("value" in otp_key and isinstance(otp_key["value"], str) and len(otp_key["value"])>0): + otp_key["value"] = c_string_to_b32encoded_string(otp_key["value"]).lower() + + set_default() if __name__ == "__main__": App = OpenChronosApp()