From 0786f35edda6fa81355e43eaa3d2c5f989b5cdd4 Mon Sep 17 00:00:00 2001 From: Oliver Matthews Date: Tue, 28 May 2013 12:30:20 +0100 Subject: [PATCH 1/3] split out sha code for legibility --- src/authenticator.c | 205 +------------------------------------------- src/sha1.c | 170 ++++++++++++++++++++++++++++++++++++ src/sha1.h | 38 ++++++++ 3 files changed, 210 insertions(+), 203 deletions(-) create mode 100644 src/sha1.c create mode 100644 src/sha1.h diff --git a/src/authenticator.c b/src/authenticator.c index 5c2ad32..015ccc1 100644 --- a/src/authenticator.c +++ b/src/authenticator.c @@ -3,6 +3,7 @@ #include "pebble_fonts.h" #include "configuration.h" +#include "sha1.h" // defined in editTzone.c extern void showEditTimeZone(); @@ -24,213 +25,11 @@ Window window; TextLayer label; TextLayer token; TextLayer ticker; + int curToken = 0; int tZone; bool changed; -/* from sha1.c from liboauth */ - -/* This code is public-domain - it is based on libcrypt - * placed in the public domain by Wei Dai and other contributors. - */ - -#include - -/* header */ - -#define HASH_LENGTH 20 -#define BLOCK_LENGTH 64 - -union _buffer { - uint8_t b[BLOCK_LENGTH]; - uint32_t w[BLOCK_LENGTH/4]; -}; - -union _state { - uint8_t b[HASH_LENGTH]; - uint32_t w[HASH_LENGTH/4]; -}; - -typedef struct sha1nfo { - union _buffer buffer; - uint8_t bufferOffset; - union _state state; - uint32_t byteCount; - uint8_t keyBuffer[BLOCK_LENGTH]; - uint8_t innerHash[HASH_LENGTH]; -} sha1nfo; - -/* public API - prototypes - TODO: doxygen*/ - -/* -void sha1_init(sha1nfo *s); -void sha1_writebyte(sha1nfo *s, uint8_t data); -void sha1_write(sha1nfo *s, const char *data, size_t len); -uint8_t* sha1_result(sha1nfo *s); -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); -uint8_t* sha1_resultHmac(sha1nfo *s); -*/ - -char* itoa(int val, int base){ - static char buf[32] = {0}; - int i = 30; - for(; val && i ; --i, val /= base) - buf[i] = "0123456789abcdef"[val % base]; - return &buf[i+1]; -} - -/* code */ -#define SHA1_K0 0x5a827999 -#define SHA1_K20 0x6ed9eba1 -#define SHA1_K40 0x8f1bbcdc -#define SHA1_K60 0xca62c1d6 - -const uint8_t sha1InitState[] = { - 0x01,0x23,0x45,0x67, // H0 - 0x89,0xab,0xcd,0xef, // H1 - 0xfe,0xdc,0xba,0x98, // H2 - 0x76,0x54,0x32,0x10, // H3 - 0xf0,0xe1,0xd2,0xc3 // H4 -}; - -void sha1_init(sha1nfo *s) { - memcpy(s->state.b,sha1InitState,HASH_LENGTH); - s->byteCount = 0; - s->bufferOffset = 0; -} - -uint32_t sha1_rol32(uint32_t number, uint8_t bits) { - return ((number << bits) | (number >> (32-bits))); -} - -void sha1_hashBlock(sha1nfo *s) { - uint8_t i; - uint32_t a,b,c,d,e,t; - - a=s->state.w[0]; - b=s->state.w[1]; - c=s->state.w[2]; - d=s->state.w[3]; - e=s->state.w[4]; - for (i=0; i<80; i++) { - if (i>=16) { - t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; - s->buffer.w[i&15] = sha1_rol32(t,1); - } - if (i<20) { - t = (d ^ (b & (c ^ d))) + SHA1_K0; - } else if (i<40) { - t = (b ^ c ^ d) + SHA1_K20; - } else if (i<60) { - t = ((b & c) | (d & (b | c))) + SHA1_K40; - } else { - t = (b ^ c ^ d) + SHA1_K60; - } - t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; - e=d; - d=c; - c=sha1_rol32(b,30); - b=a; - a=t; - } - s->state.w[0] += a; - s->state.w[1] += b; - s->state.w[2] += c; - s->state.w[3] += d; - s->state.w[4] += e; -} - -void sha1_addUncounted(sha1nfo *s, uint8_t data) { - s->buffer.b[s->bufferOffset ^ 3] = data; - s->bufferOffset++; - if (s->bufferOffset == BLOCK_LENGTH) { - sha1_hashBlock(s); - s->bufferOffset = 0; - } -} - -void sha1_writebyte(sha1nfo *s, uint8_t data) { - ++s->byteCount; - sha1_addUncounted(s, data); -} - -void sha1_write(sha1nfo *s, const char *data, size_t len) { - for (;len--;) sha1_writebyte(s, (uint8_t) *data++); -} - -void sha1_pad(sha1nfo *s) { - // Implement SHA-1 padding (fips180-2 §5.1.1) - - // Pad with 0x80 followed by 0x00 until the end of the block - sha1_addUncounted(s, 0x80); - while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); - - // Append length in the last 8 bytes - sha1_addUncounted(s, 0); // We're only using 32 bit lengths - sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths - sha1_addUncounted(s, 0); // So zero pad the top bits - sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 - sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as - sha1_addUncounted(s, s->byteCount >> 13); // byte. - sha1_addUncounted(s, s->byteCount >> 5); - sha1_addUncounted(s, s->byteCount << 3); -} - -uint8_t* sha1_result(sha1nfo *s) { - int i; - // Pad to complete the last block - sha1_pad(s); - - // Swap byte order back - for (i=0; i<5; i++) { - uint32_t a,b; - a=s->state.w[i]; - b=a<<24; - b|=(a<<8) & 0x00ff0000; - b|=(a>>8) & 0x0000ff00; - b|=a>>24; - s->state.w[i]=b; - } - - // Return pointer to hash (20 characters) - return s->state.b; -} - -#define HMAC_IPAD 0x36 -#define HMAC_OPAD 0x5c - -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { - uint8_t i; - memset(s->keyBuffer, 0, BLOCK_LENGTH); - if (keyLength > BLOCK_LENGTH) { - // Hash long keys - sha1_init(s); - for (;keyLength--;) sha1_writebyte(s, *key++); - memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); - } else { - // Block length keys are used as is - memcpy(s->keyBuffer, key, keyLength); - } - // Start inner hash - sha1_init(s); - for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); - } -} - -uint8_t* sha1_resultHmac(sha1nfo *s) { - uint8_t i; - // Complete inner hash - memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); - // Calculate outer hash - sha1_init(s); - for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); - for (i=0; iinnerHash[i]); - return sha1_result(s); -} - - -/* end sha1.c */ // return seconds since epoch compensating for Pebble's lack of location // independent GMT diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..c5a6ecd --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,170 @@ +/* from sha1.c from liboauth */ + +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ + +#include +#include "sha1.h" + +char* itoa(int val, int base){ + static char buf[32] = {0}; + int i = 30; + for(; val && i ; --i, val /= base) + buf[i] = "0123456789abcdef"[val % base]; + return &buf[i+1]; +} + +/* code */ +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +const uint8_t sha1InitState[] = { + 0x01,0x23,0x45,0x67, // H0 + 0x89,0xab,0xcd,0xef, // H1 + 0xfe,0xdc,0xba,0x98, // H2 + 0x76,0x54,0x32,0x10, // H3 + 0xf0,0xe1,0xd2,0xc3 // H4 +}; + +void sha1_init(sha1nfo *s) { + memcpy(s->state.b,sha1InitState,HASH_LENGTH); + s->byteCount = 0; + s->bufferOffset = 0; +} + +uint32_t sha1_rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void sha1_hashBlock(sha1nfo *s) { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=s->state.w[0]; + b=s->state.w[1]; + c=s->state.w[2]; + d=s->state.w[3]; + e=s->state.w[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; + s->buffer.w[i&15] = sha1_rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; + e=d; + d=c; + c=sha1_rol32(b,30); + b=a; + a=t; + } + s->state.w[0] += a; + s->state.w[1] += b; + s->state.w[2] += c; + s->state.w[3] += d; + s->state.w[4] += e; +} + +void sha1_addUncounted(sha1nfo *s, uint8_t data) { + s->buffer.b[s->bufferOffset ^ 3] = data; + s->bufferOffset++; + if (s->bufferOffset == BLOCK_LENGTH) { + sha1_hashBlock(s); + s->bufferOffset = 0; + } +} + +void sha1_writebyte(sha1nfo *s, uint8_t data) { + ++s->byteCount; + sha1_addUncounted(s, data); +} + +void sha1_write(sha1nfo *s, const char *data, size_t len) { + for (;len--;) sha1_writebyte(s, (uint8_t) *data++); +} + +void sha1_pad(sha1nfo *s) { + // Implement SHA-1 padding (fips180-2 §5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + sha1_addUncounted(s, 0x80); + while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); + + // Append length in the last 8 bytes + sha1_addUncounted(s, 0); // We're only using 32 bit lengths + sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths + sha1_addUncounted(s, 0); // So zero pad the top bits + sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 + sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as + sha1_addUncounted(s, s->byteCount >> 13); // byte. + sha1_addUncounted(s, s->byteCount >> 5); + sha1_addUncounted(s, s->byteCount << 3); +} + +uint8_t* sha1_result(sha1nfo *s) { + int i; + // Pad to complete the last block + sha1_pad(s); + + // Swap byte order back + for (i=0; i<5; i++) { + uint32_t a,b; + a=s->state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + s->state.w[i]=b; + } + + // Return pointer to hash (20 characters) + return s->state.b; +} + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { + uint8_t i; + memset(s->keyBuffer, 0, BLOCK_LENGTH); + if (keyLength > BLOCK_LENGTH) { + // Hash long keys + sha1_init(s); + for (;keyLength--;) sha1_writebyte(s, *key++); + memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); + } else { + // Block length keys are used as is + memcpy(s->keyBuffer, key, keyLength); + } + // Start inner hash + sha1_init(s); + for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); + } +} + +uint8_t* sha1_resultHmac(sha1nfo *s) { + uint8_t i; + // Complete inner hash + memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); + // Calculate outer hash + sha1_init(s); + for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); + for (i=0; iinnerHash[i]); + return sha1_result(s); +} + + +/* end sha1.c */ + diff --git a/src/sha1.h b/src/sha1.h new file mode 100644 index 0000000..6f53df5 --- /dev/null +++ b/src/sha1.h @@ -0,0 +1,38 @@ +#ifndef _SHA1_H_ +#define _SHA1_H_ + +#include "pebble_os.h" + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +}; + +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +}; + +typedef struct sha1nfo { + union _buffer buffer; + uint8_t bufferOffset; + union _state state; + uint32_t byteCount; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; +} sha1nfo; + +/* public API - prototypes - TODO: doxygen*/ + +void sha1_init(sha1nfo *s); +void sha1_writebyte(sha1nfo *s, uint8_t data); +void sha1_write(sha1nfo *s, const char *data, size_t len); +uint8_t* sha1_result(sha1nfo *s); +void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); +uint8_t* sha1_resultHmac(sha1nfo *s); +char* itoa(int val, int base); + +#endif From 769a58f270a6bf6151a613f8022a6962ef6d908c Mon Sep 17 00:00:00 2001 From: Oliver Matthews Date: Tue, 28 May 2013 14:12:57 +0100 Subject: [PATCH 2/3] initial battle.net support --- configuration.py | 52 +++++++++++++++++++++++++++++++++++++++------ configuration.txt | 4 ++++ src/authenticator.c | 19 +++++++---------- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/configuration.py b/configuration.py index 7156760..a0fc1fd 100755 --- a/configuration.py +++ b/configuration.py @@ -5,17 +5,30 @@ import base64 import sys +from binascii import unhexlify + secrets = [] labels = [] lengths = [] +code_lengths=[] +max_code_length = 6 + time_zone = "+0" -def genKeyLine( code ): +def decode( code ): key_b32 = code.replace(' ','').upper() key_b32 = key_b32+'='*(32%len(key_b32)) - key = base64.b32decode(key_b32) - key_bytes = map(ord,key) + key = base64.b32decode(key_b32) + return map(ord,key) + +def battle_decode ( code ): + code = unhexlify( code ) + key = map(ord,code) + return key + +def genKeyLine( key_bytes ): + print key_bytes lengths.append( len(key_bytes) ) key_hex = ["0x%02X" % x for x in key_bytes] return "{ " + ', '.join(key_hex) + " }," @@ -31,24 +44,49 @@ def genKeyLine( code ): time_zone = value else: labels.append( key ) - secrets.append( genKeyLine(value) ) + if( key.lower() == "battle" ): + code_lengths.append( "8" ) + max_code_length = 8 + secrets.append( genKeyLine(battle_decode(value)) ) + else: + secrets.append( genKeyLine(decode(value)) ) + code_lengths.append( "6" ) f.close() f = open( "src/configuration.h","w" ) f.write( "#ifndef _CONFIGURATION_H_\n#define _CONFIGURATION_H_\n" ) f.write( "#define NUM_SECRETS %i\n" % len(labels) ) f.write( "#define DEFAULT_TIME_ZONE %s\n" % time_zone ) -f.write( "char otplabels[NUM_SECRETS][10] = {\n " ) +f.write( "#define TOKEN_TEXT \"" ) +for _ in range( max_code_length ): + f.write( "X" ) +f.write( "\"\n") +f.write( "#define DIGITS_TRUNCATE 1" ) +for _ in range( max_code_length ): + f.write( "0" ) +f.write( "\n" ) + +#TODO:: calculate length properly +f.write( "char otplabels[NUM_SECRETS][%s] = {\n " % 20) for label in labels: f.write( "\"%s\"," % label ) f.write( "\n};\n" ) -f.write( "unsigned char otpkeys[NUM_SECRETS][16] = {\n " ) + +f.write( "unsigned char otpkeys[NUM_SECRETS][41] = {\n " ) for secret in secrets: f.write( "%s\n" % secret ) f.write( "};\n" ) + f.write ("int otpsizes[NUM_SECRETS] = { ") for length in lengths: f.write( "%s," % length ) -f.write( "};\n#endif\n" ) +f.write( "};\n") + +f.write( "int otplengths[NUM_SECRETS] = { ") +for length in code_lengths: + f.write( "%s," % length ) +f.write( "};\n") + +f.write("#endif\n" ) f.close() diff --git a/configuration.txt b/configuration.txt index 3c13f57..c177aa8 100644 --- a/configuration.txt +++ b/configuration.txt @@ -2,4 +2,8 @@ tz:+1 # define your secrets, one per line, in the form # name:secret +# for battle.net the name must be battle +# and the key is in the form of a 40 character hex string. fake:pskfb2vhfiegni2h +battle:0123456789012345678901234567890123456789 + diff --git a/src/authenticator.c b/src/authenticator.c index 015ccc1..77cbd8c 100644 --- a/src/authenticator.c +++ b/src/authenticator.c @@ -8,15 +8,12 @@ // defined in editTzone.c extern void showEditTimeZone(); -// Truncate n decimal digits to 2^n for 6 digits -#define DIGITS_TRUNCATE 1000000 - #define SHA1_SIZE 20 #define MY_UUID { 0xA4, 0xA6, 0x13, 0xB5, 0x8A, 0x6B, 0x4F, 0xF0, 0xBD, 0x80, 0x00, 0x38, 0xA1, 0x51, 0xCD, 0x86 } PBL_APP_INFO(MY_UUID, - "Authenticator", "pokey9000/IEF/rigel314", - 1, 1, /* App version */ + "Authenticator", "pokey9000/IEF/rigel314/cwoac", + 1, 3, /* App version */ RESOURCE_ID_IMAGE_MENU_ICON, APP_INFO_STANDARD_APP); @@ -61,14 +58,14 @@ void handle_second_tick(AppContextRef ctx, PebbleTickEvent *t) { (void)t; (void)ctx; - static char tokenText[] = "RYRYRY"; // Needs to be static because it's used by the system later. + static char tokenText[] = TOKEN_TEXT; // Needs to be static because it's used by the system later. sha1nfo s; uint8_t ofs; uint32_t otp; int i; uint32_t unix_time; - char sha1_time[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + unsigned char sha1_time[8] = {0, 0, 0, 0, 0, 0, 0, 0}; PblTm curTime; get_time(&curTime); @@ -89,7 +86,7 @@ void handle_second_tick(AppContextRef ctx, PebbleTickEvent *t) { // First get the HMAC hash of the time payload with the shared key sha1_initHmac(&s, otpkeys[curToken], otpsizes[curToken]); - sha1_write(&s, sha1_time, 8); + sha1_write(&s, (const char *)sha1_time, 8); sha1_resultHmac(&s); // Then do the HOTP truncation. HOTP pulls its result from a 31-bit byte @@ -104,11 +101,11 @@ void handle_second_tick(AppContextRef ctx, PebbleTickEvent *t) { otp %= DIGITS_TRUNCATE; // Convert result into a string. Sure wish we had working snprintf... - for(i = 0; i < 6; i++) { - tokenText[5-i] = 0x30 + (otp % 10); + for(i = 1; i <= otplengths[curToken]; i++) { + tokenText[otplengths[curToken]-i] = 0x30 + (otp % 10); otp /= 10; } - tokenText[6]=0; + tokenText[otplengths[curToken]]=0; char *labelText = otplabels[curToken]; From af46d9c9a94f985c18518c919feb437fb8bc6fe5 Mon Sep 17 00:00:00 2001 From: Oliver Matthews Date: Fri, 21 Jun 2013 13:01:52 +0100 Subject: [PATCH 3/3] use _ to delimit battle.net codes --- README.md | 15 +++++++++++++-- configuration.py | 9 +++++++-- configuration.txt | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6b21016..fd4f50e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Authenticator ============= -Forked off 'authenticator' by IEF, which was forked off 'twostep' by pokey9000, this is Authenticator for Pebble, with patches from rigel314 +Forked off 'authenticator' by IEF, which was forked off 'twostep' by pokey9000, this is Authenticator for Pebble, with patches from rigel314 and cwoac generating multiple Time-based One-Time Passwords, much like Google Authenticator. @@ -17,7 +17,7 @@ On most sites, when setting up choose 'show key' when presented with the QR code 3. add it to the end of configuration.txt, following the example in the format label:secret -4. repeat this for all your keys (don't forget to remove the example) +4. repeat this for all your keys (don't forget to remove the examples) 5. Generate the config by running ./configuration.py @@ -27,3 +27,14 @@ label:secret The above is assuming you have the Pebble SDK installed and configured to compile watch apps. If not, review: http://developer.getpebble.com/1/01_GetStarted/01_Step_2 + +Battle.net +---------- + +Battle.net / guild wars keys are slightly different to handle as they use a variant of the algorithm. +To mark a code as being a battle.net key, prefix it's label with an underscore ('_' - see the example configuration.txt) + +To determine your key, you will need the secret code. You can use [python-bna](https://github.com/Adys/python-bna) to do so. +Once you have your key installed in bna (either by generating a fresh one or using recover) you can get the value from ~/.config/bna/bna.conf + +Note that Blizzard seem to be stricter on the timing than google (I suspect that google actually allow you to use +/- a password) - so if you have issues, check your watch's time is correct. diff --git a/configuration.py b/configuration.py index a0fc1fd..313bb4a 100755 --- a/configuration.py +++ b/configuration.py @@ -28,7 +28,6 @@ def battle_decode ( code ): return key def genKeyLine( key_bytes ): - print key_bytes lengths.append( len(key_bytes) ) key_hex = ["0x%02X" % x for x in key_bytes] return "{ " + ', '.join(key_hex) + " }," @@ -40,11 +39,17 @@ def genKeyLine( key_bytes ): line = line.strip() if( line.startswith('#') or not ':' in line ): continue key,value = line.split(':') + + type = "google" + if( line.startswith('_') ): + type = "battle" + key = key[1:] + if( key.lower() == "tz" ): time_zone = value else: labels.append( key ) - if( key.lower() == "battle" ): + if( type == "battle" ): code_lengths.append( "8" ) max_code_length = 8 secrets.append( genKeyLine(battle_decode(value)) ) diff --git a/configuration.txt b/configuration.txt index c177aa8..5188c76 100644 --- a/configuration.txt +++ b/configuration.txt @@ -5,5 +5,5 @@ tz:+1 # for battle.net the name must be battle # and the key is in the form of a 40 character hex string. fake:pskfb2vhfiegni2h -battle:0123456789012345678901234567890123456789 +_bnet:0123456789012345678901234567890123456789