diff --git a/LICENSE b/LICENSE index 1e34fe9..7255953 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Cody Tilkins +Copyright (c) 2024 Cody Tilkins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build.bat b/build.bat index de607ba..8a01706 100644 --- a/build.bat +++ b/build.bat @@ -1,17 +1,17 @@ -@echo off - -echo Compiling -gcc -O2 -Wall -shared -c cotp.c otpuri.c - -echo Building DLL -gcc -O2 -Wall -shared -o libcotp.dll cotp.o otpuri.o -lcrypto - -echo Building static library -ar rcs -o libcotp.a cotp.o otpuri.o - -echo Building test C application -gcc -O2 -Wall -L . -I . -o test_c.exe test/main.c libcotp.a -lcrypto - -echo Building test C++ application -g++ -O2 -Wall -L . -I . -o test_cpp.exe test/main.cpp libcotp.a -lcrypto - +@echo off + +echo Compiling +gcc -O2 -Wall -shared -c cotp.c otpuri.c + +echo Building DLL +gcc -O2 -Wall -shared -o libcotp.dll cotp.o otpuri.o -lcrypto + +echo Building static library +ar rcs -o libcotp.a cotp.o otpuri.o + +echo Building test C application +gcc -O2 -Wall -L . -I . -o test_c.exe test/main.c libcotp.a -lcrypto + +echo Building test C++ application +g++ -O2 -Wall -L . -I . -o test_cpp.exe test/main.cpp libcotp.a -lcrypto + diff --git a/cotp.c b/cotp.c index fad72bc..8d3d7d0 100644 --- a/cotp.c +++ b/cotp.c @@ -1,470 +1,472 @@ -#include "cotp.h" - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <string.h> -#include <time.h> -#include <math.h> - -#include <openssl/rand.h> - -/* - Converts an OTPType enum to string. - - Returns - OTPType as string - error, 0 -*/ -const char* OTPType_asString(OTPType type) -{ - switch (type) - { - case OTP: return "OTP"; - case TOTP: return "TOTP"; - case HOTP: return "HOTP"; - } - return NULL; -} - -/* - Initializes an OTPData structure. - - OTPData is a non-initialized structure - base32_secret is a base32 compliant secret string - algo is the hmac algorithm implementation for hash and hmac - digits is the amount of output numbers for the OTP - - Only call otp_free(...) if you malloc/calloc'd the OTPData* structure - - Returns - The same pointer passed through data - error, 0 -*/ -OTPData* otp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits) -{ - data->digits = digits ? digits : 6; - data->interval = 0; - data->count = 0; - - data->method = OTP; - data->algo = algo; - data->time = NULL; - - data->base32_secret = &base32_secret[0]; - - return data; -} - -/* - Initializes an OTPData structure. Extends off of otp_new. - - OTPData is a non-initialized structure - base32_secret is a base32 compliant secret string - algo is the hmac algorithm implementation for hash and hmac - digits is the amount of output numbers for the OTP - interval is the amount of time a code is valid for in seconds - - Only call otp_free(...) if you malloc/calloc'd the OTPData* structure - - Returns - The same pointer passed through data - error, 0 -*/ -OTPData* totp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, COTP_TIME time, uint32_t digits, uint32_t interval) -{ - OTPData* tdata = otp_new(data, base32_secret, algo, digits); - tdata->interval = interval; - tdata->time = time; - tdata->method = TOTP; - - return data; -} - -/* - Initializes an OTPData structure. - - OTPData is a non-initialized structure - base32_secret is a base32 compliant secret string - algo is the hmac algorithm implementation for hash and hmac - digits is the amount of output numbers for the OTP - count is the current counter - - Only call otp_free(...) if you malloc/calloc'd the OTPData* structure - - Returns - A pointer to a new struct OTPData struct - error, 0 -*/ -OTPData* hotp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits, uint64_t count) -{ - OTPData* hdata = otp_new(data, base32_secret, algo, digits); - hdata->method = HOTP; - hdata->count = count; - - return data; -} - - -/* - Semantic convenience method. - Equivalent to free(data). -*/ -void otp_free(OTPData* data) -{ - free(data); -} - -/* - Un-base32's a base32 string stored inside an OTPData. - - out_str is the null-terminated output string already allocated - - Returns - 1 success - error, 0 -*/ -COTPRESULT otp_byte_secret(OTPData* data, char* out_str) -{ - if (out_str == NULL || strlen(data->base32_secret) % 8 != 0) - return OTP_ERROR; - - size_t base32_length = strlen(data->base32_secret); - size_t num_blocks = base32_length / 8; - size_t output_length = num_blocks * 5; - - if (output_length == 0) - return OTP_OK; - - for (size_t i = 0; i < num_blocks; i++) - { - unsigned int block_values[8] = { 0 }; - - for (int j = 0; j < 8; j++) - { - char c = data->base32_secret[i * 8 + j]; - int found = 0; - - for (int k = 0; k < 32; k++) - { - if (c == OTP_DEFAULT_BASE32_CHARS[k]) - { - block_values[j] = k; - found = 1; - break; - } - } - - if (!found) - { - return OTP_ERROR; - } - } - - out_str[i * 5] = (block_values[0] << 3) | (block_values[1] >> 2); - out_str[i * 5 + 1] = (block_values[1] << 6) | (block_values[2] << 1) | (block_values[3] >> 4); - out_str[i * 5 + 2] = (block_values[3] << 4) | (block_values[4] >> 1); - out_str[i * 5 + 3] = (block_values[4] << 7) | (block_values[5] << 2) | (block_values[6] >> 3); - out_str[i * 5 + 4] = (block_values[6] << 5) | block_values[7]; - } - - return OTP_OK; -} - -/* - Converts an integer into an 8 byte array. - - out_str is the null-terminated output string already allocated - - Returns - 1 success - error, 0 -*/ -COTPRESULT otp_num_to_bytestring(uint64_t integer, char* out_str) -{ - if (out_str == NULL) - return OTP_ERROR; - - size_t i = 7; - while (integer != 0) - { - out_str[i] = integer & 0xFF; - i--; - integer >>= 8; - } - - return OTP_OK; -} - -/* - Generates a valid secured random base32 string. - - if len <= 0, len = 16 - - len is the (strlen of out_str) - 1 - chars is the base32 charset - out_str is the null-terminated output string already allocated - - Returns - 1 on success - error, 0 - -*/ -COTPRESULT otp_random_base32(size_t len, char* out_str) -{ - if (out_str == NULL) - return OTP_ERROR; - - len = len > 0 ? len : 16; - - unsigned char rand_buffer[len]; - if (RAND_bytes(rand_buffer, len) != 1) - return OTP_ERROR; - - for (size_t i=0; i<len; i++) - { - out_str[i] = OTP_DEFAULT_BASE32_CHARS[rand_buffer[i] % 32]; - } - - return OTP_OK; -} - - -/* - Compares a key against a generated key for - a single specific timeblock. - - key is an null-terminated input string, a previous OTP generation, must be data->digits+1 long - offset is a timeblock adjustment for the generated compare key - for_time is the time the generated key will be created for - - Returns - 1 success - 0 no full comparison made - error, 0 -*/ -COTPRESULT totp_compare(OTPData* data, const char* key, int64_t offset, uint64_t for_time) -{ - char time_str[data->digits+1]; - memset(time_str, 0, data->digits+1); - - if (totp_at(data, for_time, offset, time_str) == 0) - return OTP_ERROR; - - for (size_t i=0; i<data->digits; i++) - { - if (key[i] != time_str[i]) - return OTP_ERROR; - } - - return OTP_OK; -} - -/* - Generates a OTP key using the totp algorithm. - - for_time is the time the generated key will be created for - offset is a timeblock adjustment for the generated key - out_str is the null-terminated output string already allocated - - Returns - 1 if otp key was successfully generated - error, 0 -*/ -COTPRESULT totp_at(OTPData* data, uint64_t for_time, int64_t offset, char* out_str) -{ - return otp_generate(data, totp_timecode(data, for_time) + offset, out_str); -} - -/* - Generates a OTP key using the totp algorithm with - the current, unsecure time in seconds. - - out_str is the null-terminated output string already allocated - - Returns - 1 if otp key was successfully generated - error, 0 -*/ -COTPRESULT totp_now(OTPData* data, char* out_str) -{ - return otp_generate(data, totp_timecode(data, data->time()), out_str); -} - -/* - Compares a key against a generated key for multiple - timeblocks before and after a specific time. - - key is an null-terminated input string, a previous OTP generation, must be data->digits+1 long - for_time is the time the generated key will be created for - valid_window is the number of timeblocks a OTP should be valid for - - Returns - 1 success - error, 0 -*/ -COTPRESULT totp_verify(OTPData* data, const char* key, uint64_t for_time, int64_t valid_window) -{ - if (key == NULL || valid_window < 0) - return OTP_ERROR; - - if (valid_window > 0) - { - for (int64_t i=-valid_window; i<valid_window+1; i++) - { - int cmp = totp_compare(data, key, i, for_time); - if (cmp == 1) - return cmp; - } - return OTP_ERROR; - } - - return totp_compare(data, key, 0, for_time); -} - -/* - Calculate the time in seconds relative to - for_time an OTP is valid for. - - for_time is a time in seconds - valid_window is the number of timeblocks a OTP should be valid for - - Returns - the expiration time for a code using the current OTPData configuration -*/ -uint64_t totp_valid_until(OTPData* data, uint64_t for_time, int64_t valid_window) -{ - return for_time + (data->interval * valid_window); -} - -/* - Generates the timeblock for a time in seconds. - - Timeblocks are the amount of intervals in a given time. For example, - if 1,000,000 seconds has passed for 30 second intervals, you would get - 33,333 timeblocks (intervals), where timeblock++ is effectively +30 seconds. - - for_time is a time in seconds to get the current timeblocks - - Returns - timeblock given for_time, using data->interval - error, 0 -*/ -uint64_t totp_timecode(OTPData* data, uint64_t for_time) -{ - if (data->interval <= 0) - return OTP_ERROR; - - return for_time/data->interval; -} - - -/* - Compares a key against a generated key for a single counter. - - key is an null-terminated input string, a previous OTP generation, must be data->digits+1 long - offset is a timeblock adjustment for the generated compare key - for_time is the time the generated key will be created for - - Returns - 1 success - 0 no full comparison made - error, 0 -*/ -int hotp_compare(OTPData* data, const char* key, uint64_t counter) -{ - if (key == NULL) - return OTP_ERROR; - - char cnt_str[data->digits+1]; - memset(cnt_str, 0, data->digits+1); - - if (hotp_at(data, counter, cnt_str) == 0) - return OTP_ERROR; - - for (size_t i=0; i<data->digits; i++) - { - if (key[i] != cnt_str[i]) - return OTP_ERROR; - } - - return OTP_OK; -} - -/* - Generates a OTP key using the hotp algorithm. - - counter is the counter the generated key will be created for - out_str is the null-terminated output string already allocated - - Returns - 1 if otp key was successfully generated - error, 0 -*/ -int hotp_at(OTPData* data, uint64_t counter, char* out_str) -{ - return otp_generate(data, counter, out_str); -} - -/* - Generates a OTP key using the hotp algorithm and advances the counter. - - out_str is the null-terminated output string already allocated - - Returns - 1 if otp key was successfully generated - error, 0 -*/ -int hotp_next(OTPData* data, char* out_str) -{ - return otp_generate(data, data->count++, out_str); -} - -/* - Generates an OTP (One Time Password). - - input is a number used to generate the OTP - out_str is the null-terminated output string already allocated - - Returns - 1 if otp code was successfully generated - error, 0 -*/ -COTPRESULT otp_generate(OTPData* data, uint64_t input, char* out_str) -{ - if (out_str == NULL) - return OTP_ERROR; - - char byte_string[8+1]; - memset(byte_string, 0, 8+1); - - size_t bs_len = (strlen(data->base32_secret)/8)*5 + 1; - char byte_secret[bs_len]; - memset(byte_secret, 0, bs_len); - - char hmac[64+1]; - memset(hmac, 0, 64+1); - - if (otp_num_to_bytestring(input, byte_string) == 0 - || otp_byte_secret(data, byte_secret) == 0) - return OTP_ERROR; - - int hmac_len = (*(data->algo))(byte_secret, byte_string, hmac); - if (hmac_len == 0) - return OTP_ERROR; - - uint64_t offset = (hmac[hmac_len - 1] & 0xF); - uint64_t code = - (((hmac[offset] & 0x7F) << 24) - | ((hmac[offset+1] & 0xFF) << 16) - | ((hmac[offset+2] & 0xFF) << 8) - | ((hmac[offset+3] & 0xFF))); - - static const uint64_t POWERS[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; - code %= (uint64_t) POWERS[data->digits]; - - sprintf(out_str, "%0*" PRIu64, data->digits, code); - - return OTP_OK; -} - +#include "cotp.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <time.h> +#include <math.h> + +#include <openssl/rand.h> + +/* + Converts an OTPType enum to string. + + Returns + OTPType as string + error, 0 +*/ +const char* OTPType_asString(OTPType type) +{ + switch (type) + { + case OTP: return "OTP"; + case TOTP: return "TOTP"; + case HOTP: return "HOTP"; + } + return NULL; +} + +/* + Initializes an OTPData structure. + + OTPData is a non-initialized structure + base32_secret is a base32 compliant secret string + algo is the hmac algorithm implementation for hash and hmac + digits is the amount of output numbers for the OTP + + Only call otp_free(...) if you malloc/calloc'd the OTPData* structure + + Returns + The same pointer passed through data + error, 0 +*/ +OTPData* otp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits) +{ + data->digits = digits ? digits : 6; + data->interval = 0; + data->count = 0; + + data->method = OTP; + data->algo = algo; + data->time = NULL; + + data->base32_secret = &base32_secret[0]; + + return data; +} + +/* + Initializes an OTPData structure. Extends off of otp_new. + + OTPData is a non-initialized structure + base32_secret is a base32 compliant secret string + algo is the hmac algorithm implementation for hash and hmac + digits is the amount of output numbers for the OTP + interval is the amount of time a code is valid for in seconds + + Only call otp_free(...) if you malloc/calloc'd the OTPData* structure + + Returns + The same pointer passed through data + error, 0 +*/ +OTPData* totp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, COTP_TIME time, uint32_t digits, uint32_t interval) +{ + OTPData* tdata = otp_new(data, base32_secret, algo, digits); + tdata->interval = interval; + tdata->time = time; + tdata->method = TOTP; + + return data; +} + +/* + Initializes an OTPData structure. + + OTPData is a non-initialized structure + base32_secret is a base32 compliant secret string + algo is the hmac algorithm implementation for hash and hmac + digits is the amount of output numbers for the OTP + count is the current counter + + Only call otp_free(...) if you malloc/calloc'd the OTPData* structure + + Returns + A pointer to a new struct OTPData struct + error, 0 +*/ +OTPData* hotp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits, uint64_t count) +{ + OTPData* hdata = otp_new(data, base32_secret, algo, digits); + hdata->method = HOTP; + hdata->count = count; + + return data; +} + + +/* + Semantic convenience method. + Equivalent to free(data). +*/ +void otp_free(OTPData* data) +{ + free(data); +} + +/* + Un-base32's a base32 string stored inside an OTPData. + + out_str is the null-terminated output string already allocated + + Returns + 1 success + error, 0 +*/ +COTPRESULT otp_byte_secret(OTPData* data, char* out_str) +{ + if (out_str == NULL || strlen(data->base32_secret) % 8 != 0) + return OTP_ERROR; + + size_t base32_length = strlen(data->base32_secret); + size_t num_blocks = base32_length / 8; + size_t output_length = num_blocks * 5; + + if (output_length == 0) + return OTP_OK; + + for (size_t i = 0; i < num_blocks; i++) + { + unsigned int block_values[8] = { 0 }; + + for (int j = 0; j < 8; j++) + { + char c = data->base32_secret[i * 8 + j]; + if (c == '=') + break; + + int found = 0; + for (int k = 0; k < 32; k++) + { + if (c == OTP_DEFAULT_BASE32_CHARS[k]) + { + block_values[j] = k; + found = 1; + break; + } + } + + if (!found) + { + return OTP_ERROR; + } + } + + out_str[i * 5] = (block_values[0] << 3) | (block_values[1] >> 2); + out_str[i * 5 + 1] = (block_values[1] << 6) | (block_values[2] << 1) | (block_values[3] >> 4); + out_str[i * 5 + 2] = (block_values[3] << 4) | (block_values[4] >> 1); + out_str[i * 5 + 3] = (block_values[4] << 7) | (block_values[5] << 2) | (block_values[6] >> 3); + out_str[i * 5 + 4] = (block_values[6] << 5) | block_values[7]; + } + + return OTP_OK; +} + +/* + Converts an integer into an 8 byte array. + + out_str is the null-terminated output string already allocated + + Returns + 1 success + error, 0 +*/ +COTPRESULT otp_num_to_bytestring(uint64_t integer, char* out_str) +{ + if (out_str == NULL) + return OTP_ERROR; + + size_t i = 7; + while (integer != 0) + { + out_str[i] = integer & 0xFF; + i--; + integer >>= 8; + } + + return OTP_OK; +} + +/* + Generates a valid secured random base32 string. + + if len <= 0, len = 16 + + len is the (strlen of out_str) - 1 + chars is the base32 charset + out_str is the null-terminated output string already allocated + + Returns + 1 on success + error, 0 + +*/ +COTPRESULT otp_random_base32(size_t len, char* out_str) +{ + if (out_str == NULL) + return OTP_ERROR; + + len = len > 0 ? len : 16; + + unsigned char rand_buffer[len]; + if (RAND_bytes(rand_buffer, len) != 1) + return OTP_ERROR; + + for (size_t i=0; i<len; i++) + { + out_str[i] = OTP_DEFAULT_BASE32_CHARS[rand_buffer[i] % 32]; + } + + return OTP_OK; +} + + +/* + Compares a key against a generated key for + a single specific timeblock. + + key is an null-terminated input string, a previous OTP generation, must be data->digits+1 long + offset is a timeblock adjustment for the generated compare key + for_time is the time the generated key will be created for + + Returns + 1 success + 0 no full comparison made + error, 0 +*/ +COTPRESULT totp_compare(OTPData* data, const char* key, int64_t offset, uint64_t for_time) +{ + char time_str[data->digits+1]; + memset(time_str, 0, data->digits+1); + + if (totp_at(data, for_time, offset, time_str) == 0) + return OTP_ERROR; + + for (size_t i=0; i<data->digits; i++) + { + if (key[i] != time_str[i]) + return OTP_ERROR; + } + + return OTP_OK; +} + +/* + Generates a OTP key using the totp algorithm. + + for_time is the time the generated key will be created for + offset is a timeblock adjustment for the generated key + out_str is the null-terminated output string already allocated + + Returns + 1 if otp key was successfully generated + error, 0 +*/ +COTPRESULT totp_at(OTPData* data, uint64_t for_time, int64_t offset, char* out_str) +{ + return otp_generate(data, totp_timecode(data, for_time) + offset, out_str); +} + +/* + Generates a OTP key using the totp algorithm with + the current, unsecure time in seconds. + + out_str is the null-terminated output string already allocated + + Returns + 1 if otp key was successfully generated + error, 0 +*/ +COTPRESULT totp_now(OTPData* data, char* out_str) +{ + return otp_generate(data, totp_timecode(data, data->time()), out_str); +} + +/* + Compares a key against a generated key for multiple + timeblocks before and after a specific time. + + key is an null-terminated input string, a previous OTP generation, must be data->digits+1 long + for_time is the time the generated key will be created for + valid_window is the number of timeblocks a OTP should be valid for + + Returns + 1 success + error, 0 +*/ +COTPRESULT totp_verify(OTPData* data, const char* key, uint64_t for_time, int64_t valid_window) +{ + if (key == NULL || valid_window < 0) + return OTP_ERROR; + + if (valid_window > 0) + { + for (int64_t i=-valid_window; i<valid_window+1; i++) + { + int cmp = totp_compare(data, key, i, for_time); + if (cmp == 1) + return cmp; + } + return OTP_ERROR; + } + + return totp_compare(data, key, 0, for_time); +} + +/* + Calculate the time in seconds relative to + for_time an OTP is valid for. + + for_time is a time in seconds + valid_window is the number of timeblocks a OTP should be valid for + + Returns + the expiration time for a code using the current OTPData configuration +*/ +uint64_t totp_valid_until(OTPData* data, uint64_t for_time, int64_t valid_window) +{ + return for_time + (data->interval * valid_window); +} + +/* + Generates the timeblock for a time in seconds. + + Timeblocks are the amount of intervals in a given time. For example, + if 1,000,000 seconds has passed for 30 second intervals, you would get + 33,333 timeblocks (intervals), where timeblock++ is effectively +30 seconds. + + for_time is a time in seconds to get the current timeblocks + + Returns + timeblock given for_time, using data->interval + error, 0 +*/ +uint64_t totp_timecode(OTPData* data, uint64_t for_time) +{ + if (data->interval <= 0) + return OTP_ERROR; + + return for_time/data->interval; +} + + +/* + Compares a key against a generated key for a single counter. + + key is an null-terminated input string, a previous OTP generation, must be data->digits+1 long + offset is a timeblock adjustment for the generated compare key + for_time is the time the generated key will be created for + + Returns + 1 success + 0 no full comparison made + error, 0 +*/ +int hotp_compare(OTPData* data, const char* key, uint64_t counter) +{ + if (key == NULL) + return OTP_ERROR; + + char cnt_str[data->digits+1]; + memset(cnt_str, 0, data->digits+1); + + if (hotp_at(data, counter, cnt_str) == 0) + return OTP_ERROR; + + for (size_t i=0; i<data->digits; i++) + { + if (key[i] != cnt_str[i]) + return OTP_ERROR; + } + + return OTP_OK; +} + +/* + Generates a OTP key using the hotp algorithm. + + counter is the counter the generated key will be created for + out_str is the null-terminated output string already allocated + + Returns + 1 if otp key was successfully generated + error, 0 +*/ +int hotp_at(OTPData* data, uint64_t counter, char* out_str) +{ + return otp_generate(data, counter, out_str); +} + +/* + Generates a OTP key using the hotp algorithm and advances the counter. + + out_str is the null-terminated output string already allocated + + Returns + 1 if otp key was successfully generated + error, 0 +*/ +int hotp_next(OTPData* data, char* out_str) +{ + return otp_generate(data, data->count++, out_str); +} + +/* + Generates an OTP (One Time Password). + + input is a number used to generate the OTP + out_str is the null-terminated output string already allocated + + Returns + 1 if otp code was successfully generated + error, 0 +*/ +COTPRESULT otp_generate(OTPData* data, uint64_t input, char* out_str) +{ + if (out_str == NULL) + return OTP_ERROR; + + char byte_string[8+1]; + memset(byte_string, 0, 8+1); + + size_t bs_len = (strlen(data->base32_secret)/8)*5; + char byte_secret[bs_len + 1]; + memset(byte_secret, 0, bs_len + 1); + + char hmac[64+1]; + memset(hmac, 0, 64+1); + + if (otp_num_to_bytestring(input, byte_string) == 0 + || otp_byte_secret(data, byte_secret) == 0) + return OTP_ERROR; + + int hmac_len = (*(data->algo))(byte_secret, bs_len, byte_string, hmac); + if (hmac_len == 0) + return OTP_ERROR; + + uint64_t offset = (hmac[hmac_len - 1] & 0xF); + uint64_t code = + (((hmac[offset] & 0x7F) << 24) + | ((hmac[offset+1] & 0xFF) << 16) + | ((hmac[offset+2] & 0xFF) << 8) + | ((hmac[offset+3] & 0xFF))); + + static const uint64_t POWERS[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + code %= (uint64_t) POWERS[data->digits]; + + sprintf(out_str, "%0*" PRIu64, data->digits, code); + + return OTP_OK; +} + diff --git a/cotp.h b/cotp.h index 4a59e8e..0aca353 100644 --- a/cotp.h +++ b/cotp.h @@ -1,116 +1,115 @@ -#pragma once - -#include <stdlib.h> -#include <stdint.h> - - -// OTPRESULT can either be 1 (success) or 0 (error) -typedef int COTPRESULT; - -#define OTP_OK ((COTPRESULT) 1) -#define OTP_ERROR ((COTPRESULT) 0) - - -/* - Default characters used in BASE32 digests. - For use with otp_random_base32() -*/ -static const char OTP_DEFAULT_BASE32_CHARS[32] = -{ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', - '6', '7' -}; - - -/* - Used for differentiation on which - method you are using. Necessary - when you go to generate a URI. -*/ -typedef enum OTPType -{ - OTP, TOTP, HOTP -} OTPType; - -const char* OTPType_asString(OTPType type); - - -/* - Must compute HMAC using passed arguments, - output as char array through output. - - key is base32 secret key. - input is input number as string. - output is an output char buffer of the resulting HMAC operation. - - Must return 0 if error, or the length in bytes of the HMAC operation. -*/ -typedef int (*COTP_ALGO)(const char* key, const char* input, char* output); - -/* - Must return the current time in seconds. -*/ -typedef uint64_t (*COTP_TIME)(); - - -/* - Holds data for use by the cotp module. - - If you know what you are doing, - feel free to initialize this yourself. -*/ -typedef struct OTPData -{ - uint32_t digits; - uint32_t interval; // TOTP exclusive - uint64_t count; - - OTPType method; - COTP_ALGO algo; - COTP_TIME time; - - const char* base32_secret; -} OTPData; - - -/* - Struct initialization functions -*/ -OTPData* otp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits); -OTPData* totp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, COTP_TIME time, uint32_t digits, uint32_t interval); -OTPData* hotp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits, uint64_t count); - -/* - OTP free function -*/ -void otp_free(OTPData* data); - -/* - OTP functions -*/ -COTPRESULT otp_generate(OTPData* data, uint64_t input, char* out_str); -COTPRESULT otp_byte_secret(OTPData* data, char* out_str); -COTPRESULT otp_num_to_bytestring(uint64_t integer, char* out_str); -COTPRESULT otp_random_base32(size_t len, char* out_str); - - -/* - TOTP functions -*/ -COTPRESULT totp_compare(OTPData* data, const char* key, int64_t offset, uint64_t for_time); -COTPRESULT totp_at(OTPData* data, uint64_t for_time, int64_t offset, char* out_str); -COTPRESULT totp_now(OTPData* data, char* out_str); -COTPRESULT totp_verify(OTPData* data, const char* key, uint64_t for_time, int64_t valid_window); -uint64_t totp_valid_until(OTPData* data, uint64_t for_time, int64_t valid_window); -uint64_t totp_timecode(OTPData* data, uint64_t for_time); - - -/* - HOTP functions -*/ -COTPRESULT hotp_compare(OTPData* data, const char* key, uint64_t counter); -COTPRESULT hotp_at(OTPData* data, uint64_t counter, char* out_str); -COTPRESULT hotp_next(OTPData* data, char* out_str); - +#pragma once + +#include <stdlib.h> +#include <stdint.h> + + +// OTPRESULT can either be 1 (success) or 0 (error) +typedef int COTPRESULT; + +#define OTP_OK ((COTPRESULT) 1) +#define OTP_ERROR ((COTPRESULT) 0) + + +/* + Default characters used in BASE32 digests. + For use with otp_random_base32() +*/ +static const char OTP_DEFAULT_BASE32_CHARS[32] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', + '6', '7' +}; + + +/* + Used for differentiation on which + method you are using. Necessary + when you go to generate a URI. +*/ +typedef enum OTPType +{ + OTP, TOTP, HOTP +} OTPType; + +const char* OTPType_asString(OTPType type); + + +/* + Must compute HMAC using passed arguments, + output as char array through output. + + key is base32 secret key. + input is input number as string. + output is an output char buffer of the resulting HMAC operation. + + Must return 0 if error, or the length in bytes of the HMAC operation. +*/ +typedef int (*COTP_ALGO)(const char* key, int key_length, const char* input, char* output); + +/* + Must return the current time in seconds. +*/ +typedef uint64_t (*COTP_TIME)(); + + +/* + Holds data for use by the cotp module. + + If you know what you are doing, + feel free to initialize this yourself. +*/ +typedef struct OTPData +{ + uint32_t digits; + uint32_t interval; // TOTP exclusive + uint64_t count; + + OTPType method; + COTP_ALGO algo; + COTP_TIME time; + + const char* base32_secret; +} OTPData; + + +/* + Struct initialization functions +*/ +OTPData* otp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits); +OTPData* totp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, COTP_TIME time, uint32_t digits, uint32_t interval); +OTPData* hotp_new(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits, uint64_t count); + +/* + OTP free function +*/ +void otp_free(OTPData* data); + +/* + OTP functions +*/ +COTPRESULT otp_generate(OTPData* data, uint64_t input, char* out_str); +COTPRESULT otp_byte_secret(OTPData* data, char* out_str); +COTPRESULT otp_num_to_bytestring(uint64_t integer, char* out_str); +COTPRESULT otp_random_base32(size_t len, char* out_str); + + +/* + TOTP functions +*/ +COTPRESULT totp_compare(OTPData* data, const char* key, int64_t offset, uint64_t for_time); +COTPRESULT totp_at(OTPData* data, uint64_t for_time, int64_t offset, char* out_str); +COTPRESULT totp_now(OTPData* data, char* out_str); +COTPRESULT totp_verify(OTPData* data, const char* key, uint64_t for_time, int64_t valid_window); +uint64_t totp_valid_until(OTPData* data, uint64_t for_time, int64_t valid_window); +uint64_t totp_timecode(OTPData* data, uint64_t for_time); + + +/* + HOTP functions +*/ +COTPRESULT hotp_compare(OTPData* data, const char* key, uint64_t counter); +COTPRESULT hotp_at(OTPData* data, uint64_t counter, char* out_str); +COTPRESULT hotp_next(OTPData* data, char* out_str); diff --git a/cotp.hpp b/cotp.hpp index 8b895de..7522f07 100644 --- a/cotp.hpp +++ b/cotp.hpp @@ -1,191 +1,191 @@ - -#pragma once - -#if defined(__cplusplus) - -extern "C" -{ - #include "cotp.h" - #include "otpuri.h" -} - -#include <cstdint> - -namespace COTP -{ - - class OTP - { - protected: - OTPData* data; - - public: - OTP(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits) - { - this->data = otp_new(data, base32_secret, algo, digits); - } - - ~OTP() - { - data = nullptr; - } - - COTPRESULT generate(int64_t input, char* output) - { - return otp_generate(data, input, output); - } - - COTPRESULT byte_secret(char* out_str) - { - return otp_byte_secret(data, out_str); - } - - COTPRESULT num_to_bytestring(uint64_t integer, char* out_str) - { - return otp_num_to_bytestring(integer, out_str); - } - - size_t uri_strlen(const char* issuer, const char* name, const char* digest) - { - return otpuri_strlen(data, issuer, name, digest); - } - - COTPRESULT build_uri(const char* issuer, const char* name, const char* digest, char* output) - { - return otpuri_build_uri(data, issuer, name, digest, output); - } - - OTPData* data_struct() - { - return data; - } - - static COTPRESULT random_base32(size_t len, char* out_str) - { - return otp_random_base32(len, out_str); - } - - }; - - class TOTP - { - protected: - OTPData* data; - - public: - TOTP(OTPData* data, const char* base32_secret, COTP_ALGO algo, COTP_TIME time, uint32_t digits, uint32_t interval) - { - this->data = totp_new(data, base32_secret, algo, time, digits, interval); - } - - ~TOTP() - { - data = nullptr; - } - - COTPRESULT at(uint64_t for_time, uint64_t offset, char* out_str) - { - return totp_at(data, for_time, offset, out_str); - } - - COTPRESULT now(char* out_str) - { - return totp_now(data, out_str); - } - - COTPRESULT verify(const char* key, uint64_t for_time, int64_t valid_window) - { - return totp_verify(data, key, for_time, valid_window); - } - - uint64_t valid_until(uint64_t for_time, int64_t valid_window) - { - return totp_valid_until(data, for_time, valid_window); - } - - uint64_t timecode(uint64_t for_time) - { - return totp_timecode(data, for_time); - } - - size_t uri_strlen(const char* issuer, const char* name, const char* digest) - { - return otpuri_strlen(data, issuer, name, digest); - } - - COTPRESULT build_uri(const char* issuer, const char* name, const char* digest, char* output) - { - return otpuri_build_uri(data, issuer, name, digest, output); - } - - OTPData* data_struct() - { - return data; - } - - static COTPRESULT random_base32(size_t len, char* out_str) - { - return otp_random_base32(len, out_str); - } - - }; - - class HOTP - { - protected: - OTPData* data; - - public: - HOTP(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits, uint64_t count) - { - this->data = hotp_new(data, base32_secret, algo, digits, count); - } - - ~HOTP() - { - data = nullptr; - } - - COTPRESULT at(uint64_t counter, char* out_str) - { - return hotp_at(data, counter, out_str); - } - - COTPRESULT next(char* out_str) - { - return hotp_next(data, out_str); - } - - COTPRESULT compare(const char* key, uint64_t counter) - { - return hotp_compare(data, key, counter); - } - - size_t uri_strlen(const char* issuer, const char* name, const char* digest) - { - return otpuri_strlen(data, issuer, name, digest); - } - - COTPRESULT build_uri(const char* issuer, const char* name, const char* digest, char* output) - { - return otpuri_build_uri(data, issuer, name, digest, output); - } - - OTPData* data_struct() - { - return data; - } - - static COTPRESULT random_base32(size_t len, char* out_str) - { - return otp_random_base32(len, out_str); - } - - }; - -} // namespace COTP - -#else -# error "cotp.hpp is a C++ header. __cplusplus not defined." -#endif - + +#pragma once + +#if defined(__cplusplus) + +extern "C" +{ + #include "cotp.h" + #include "otpuri.h" +} + +#include <cstdint> + +namespace COTP +{ + + class OTP + { + protected: + OTPData* data; + + public: + OTP(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits) + { + this->data = otp_new(data, base32_secret, algo, digits); + } + + ~OTP() + { + data = nullptr; + } + + COTPRESULT generate(int64_t input, char* output) + { + return otp_generate(data, input, output); + } + + COTPRESULT byte_secret(char* out_str) + { + return otp_byte_secret(data, out_str); + } + + COTPRESULT num_to_bytestring(uint64_t integer, char* out_str) + { + return otp_num_to_bytestring(integer, out_str); + } + + size_t uri_strlen(const char* issuer, const char* name, const char* digest) + { + return otpuri_strlen(data, issuer, name, digest); + } + + COTPRESULT build_uri(const char* issuer, const char* name, const char* digest, char* output) + { + return otpuri_build_uri(data, issuer, name, digest, output); + } + + OTPData* data_struct() + { + return data; + } + + static COTPRESULT random_base32(size_t len, char* out_str) + { + return otp_random_base32(len, out_str); + } + + }; + + class TOTP + { + protected: + OTPData* data; + + public: + TOTP(OTPData* data, const char* base32_secret, COTP_ALGO algo, COTP_TIME time, uint32_t digits, uint32_t interval) + { + this->data = totp_new(data, base32_secret, algo, time, digits, interval); + } + + ~TOTP() + { + data = nullptr; + } + + COTPRESULT at(uint64_t for_time, uint64_t offset, char* out_str) + { + return totp_at(data, for_time, offset, out_str); + } + + COTPRESULT now(char* out_str) + { + return totp_now(data, out_str); + } + + COTPRESULT verify(const char* key, uint64_t for_time, int64_t valid_window) + { + return totp_verify(data, key, for_time, valid_window); + } + + uint64_t valid_until(uint64_t for_time, int64_t valid_window) + { + return totp_valid_until(data, for_time, valid_window); + } + + uint64_t timecode(uint64_t for_time) + { + return totp_timecode(data, for_time); + } + + size_t uri_strlen(const char* issuer, const char* name, const char* digest) + { + return otpuri_strlen(data, issuer, name, digest); + } + + COTPRESULT build_uri(const char* issuer, const char* name, const char* digest, char* output) + { + return otpuri_build_uri(data, issuer, name, digest, output); + } + + OTPData* data_struct() + { + return data; + } + + static COTPRESULT random_base32(size_t len, char* out_str) + { + return otp_random_base32(len, out_str); + } + + }; + + class HOTP + { + protected: + OTPData* data; + + public: + HOTP(OTPData* data, const char* base32_secret, COTP_ALGO algo, uint32_t digits, uint64_t count) + { + this->data = hotp_new(data, base32_secret, algo, digits, count); + } + + ~HOTP() + { + data = nullptr; + } + + COTPRESULT at(uint64_t counter, char* out_str) + { + return hotp_at(data, counter, out_str); + } + + COTPRESULT next(char* out_str) + { + return hotp_next(data, out_str); + } + + COTPRESULT compare(const char* key, uint64_t counter) + { + return hotp_compare(data, key, counter); + } + + size_t uri_strlen(const char* issuer, const char* name, const char* digest) + { + return otpuri_strlen(data, issuer, name, digest); + } + + COTPRESULT build_uri(const char* issuer, const char* name, const char* digest, char* output) + { + return otpuri_build_uri(data, issuer, name, digest, output); + } + + OTPData* data_struct() + { + return data; + } + + static COTPRESULT random_base32(size_t len, char* out_str) + { + return otp_random_base32(len, out_str); + } + + }; + +} // namespace COTP + +#else +# error "cotp.hpp is a C++ header. __cplusplus not defined." +#endif + diff --git a/otpuri.c b/otpuri.c index 42ec716..713b4a3 100644 --- a/otpuri.c +++ b/otpuri.c @@ -1,160 +1,160 @@ - -#include "otpuri.h" - -#include <stdio.h> -#include <string.h> -#include <inttypes.h> - - -/* - Encodes all given data into url-safe data. Null-terminates - returned string. Caller must free the returned pointer. - Will treat embedded \0's as valid characters. - - length is the length in bytes of input string data - data is the optionally null-terminated string to encode - - Returns - Pointer to malloc'd url-safe data string - error, 0 -*/ -COTPRESULT otpuri_encode_url(const char* data, size_t length, char* output) -{ - if (data == NULL || output == NULL) - return OTP_ERROR; - - static const char to_test[] = "\"<>#%@{}|\\^~[]` ?&"; - - size_t output_i = 0; - for (size_t i=0; i<length; i++) - { - output[output_i] = data[i]; - if (data[i] < 0x20 || data[i] >= 0x7F) - { - output_i += snprintf(output + output_i, 3+1, "%%%.2X", data[i]); - output_i--; - } - else - { - for (size_t j=0; j<18; j++) - { - if (to_test[j] == data[i]) - { - output_i += snprintf(output + output_i, 3+1, "%%%.2X", data[i]); - output_i--; - break; - } - } - } - output_i++; - } - - return OTP_OK; -} - -/* - Returns the maximum expected length of an array needed to fill a buffer - with an otpuri not including the null-termination. - - Returns - Length in bytes of an array to match an otpuri generation -*/ -size_t otpuri_strlen(OTPData* data, const char* issuer, const char* name, const char* digest) -{ - return strlen(issuer) * 2 * 3 - + strlen(name) * 3 - + strlen(data->base32_secret) * 3 - + strlen(digest) * 3 - + 100; -} - -/* - Builds a valid, url-safe URI which is used for applications such as QR codes. - - issuer is the null-terminated string of the company name - name is the null-terminated string of the username - digest is the null-terminated string of the HMAC encryption algorithm - output is the zero'd destination the function writes the URI to - - Returns - 1 on success - error, 0 - -*/ -COTPRESULT otpuri_build_uri(OTPData* data, const char* issuer, const char* name, const char* digest, char* output) -{ - if (issuer == NULL || name == NULL || digest == NULL || output == NULL) - return OTP_ERROR; - - strcat(output, "otpuri://"); - switch(data->method) - { - case TOTP: - strcat(output, "totp"); - break; - case HOTP: - strcat(output, "hotp"); - break; - default: - strcat(output, "otp"); - break; - } - - strcat(output, "/"); - - char cissuer[strlen(issuer)*3 + 1]; - memset(cissuer, 0, strlen(issuer)*3 + 1); - otpuri_encode_url(issuer, strlen(issuer), cissuer); - strcat(output, cissuer); - - strcat(output, ":"); - - char cname[strlen(name)*3 + 1]; - memset(cname, 0, strlen(name)*3 + 1); - otpuri_encode_url(name, strlen(name), cname); - strcat(output, cname); - - strcat(output, "?secret="); - char csecret[strlen(data->base32_secret)*3 + 1]; - memset(csecret, 0, strlen(data->base32_secret)*3 + 1); - otpuri_encode_url(data->base32_secret, strlen(data->base32_secret), csecret); - strcat(output, csecret); - - strcat(output, "&issuer="); - strcat(output, cissuer); - - strcat(output, "&algorithm="); - char cdigest[strlen(digest)*3 + 1]; - memset(cdigest, 0, strlen(digest)*3 + 1); - otpuri_encode_url(digest, strlen(digest), cdigest); - strcat(output, cdigest); - - strcat(output, "&digits="); - char cdigits[21]; - memset(cdigits, 0, 21); - snprintf(cdigits, 21, "%" PRIu32, data->digits); - strcat(output, cdigits); - - switch(data->method) - { - case TOTP: - strcat(output, "&period="); - char cperiod[21]; - memset(cperiod, 0, 21); - snprintf(cperiod, 21, "%" PRIu32, data->interval); - strcat(output, cperiod); - break; - case HOTP: - strcat(output, "&counter="); - char ccounter[21]; - memset(ccounter, 0, 21); - snprintf(ccounter, 21, "%" PRIu64, data->count); - strcat(output, ccounter); - break; - default: - break; - } - - return OTP_OK; -} - + +#include "otpuri.h" + +#include <stdio.h> +#include <string.h> +#include <inttypes.h> + + +/* + Encodes all given data into url-safe data. Null-terminates + returned string. Caller must free the returned pointer. + Will treat embedded \0's as valid characters. + + length is the length in bytes of input string data + data is the optionally null-terminated string to encode + + Returns + Pointer to malloc'd url-safe data string + error, 0 +*/ +COTPRESULT otpuri_encode_url(const char* data, size_t length, char* output) +{ + if (data == NULL || output == NULL) + return OTP_ERROR; + + static const char to_test[] = "\"<>#%@{}|\\^~[]` ?&"; + + size_t output_i = 0; + for (size_t i=0; i<length; i++) + { + output[output_i] = data[i]; + if (data[i] < 0x20 || data[i] >= 0x7F) + { + output_i += snprintf(output + output_i, 3+1, "%%%.2X", data[i]); + output_i--; + } + else + { + for (size_t j=0; j<18; j++) + { + if (to_test[j] == data[i]) + { + output_i += snprintf(output + output_i, 3+1, "%%%.2X", data[i]); + output_i--; + break; + } + } + } + output_i++; + } + + return OTP_OK; +} + +/* + Returns the maximum expected length of an array needed to fill a buffer + with an otpuri not including the null-termination. + + Returns + Length in bytes of an array to match an otpuri generation +*/ +size_t otpuri_strlen(OTPData* data, const char* issuer, const char* name, const char* digest) +{ + return strlen(issuer) * 2 * 3 + + strlen(name) * 3 + + strlen(data->base32_secret) * 3 + + strlen(digest) * 3 + + 100; +} + +/* + Builds a valid, url-safe URI which is used for applications such as QR codes. + + issuer is the null-terminated string of the company name + name is the null-terminated string of the username + digest is the null-terminated string of the HMAC encryption algorithm + output is the zero'd destination the function writes the URI to + + Returns + 1 on success + error, 0 + +*/ +COTPRESULT otpuri_build_uri(OTPData* data, const char* issuer, const char* name, const char* digest, char* output) +{ + if (issuer == NULL || name == NULL || digest == NULL || output == NULL) + return OTP_ERROR; + + strcat(output, "otpuri://"); + switch(data->method) + { + case TOTP: + strcat(output, "totp"); + break; + case HOTP: + strcat(output, "hotp"); + break; + default: + strcat(output, "otp"); + break; + } + + strcat(output, "/"); + + char cissuer[strlen(issuer)*3 + 1]; + memset(cissuer, 0, strlen(issuer)*3 + 1); + otpuri_encode_url(issuer, strlen(issuer), cissuer); + strcat(output, cissuer); + + strcat(output, ":"); + + char cname[strlen(name)*3 + 1]; + memset(cname, 0, strlen(name)*3 + 1); + otpuri_encode_url(name, strlen(name), cname); + strcat(output, cname); + + strcat(output, "?secret="); + char csecret[strlen(data->base32_secret)*3 + 1]; + memset(csecret, 0, strlen(data->base32_secret)*3 + 1); + otpuri_encode_url(data->base32_secret, strlen(data->base32_secret), csecret); + strcat(output, csecret); + + strcat(output, "&issuer="); + strcat(output, cissuer); + + strcat(output, "&algorithm="); + char cdigest[strlen(digest)*3 + 1]; + memset(cdigest, 0, strlen(digest)*3 + 1); + otpuri_encode_url(digest, strlen(digest), cdigest); + strcat(output, cdigest); + + strcat(output, "&digits="); + char cdigits[21]; + memset(cdigits, 0, 21); + snprintf(cdigits, 21, "%" PRIu32, data->digits); + strcat(output, cdigits); + + switch(data->method) + { + case TOTP: + strcat(output, "&period="); + char cperiod[21]; + memset(cperiod, 0, 21); + snprintf(cperiod, 21, "%" PRIu32, data->interval); + strcat(output, cperiod); + break; + case HOTP: + strcat(output, "&counter="); + char ccounter[21]; + memset(ccounter, 0, 21); + snprintf(ccounter, 21, "%" PRIu64, data->count); + strcat(output, ccounter); + break; + default: + break; + } + + return OTP_OK; +} + diff --git a/otpuri.h b/otpuri.h index b7ec999..26f174a 100644 --- a/otpuri.h +++ b/otpuri.h @@ -1,7 +1,7 @@ -#pragma once - -#include "cotp.h" - -size_t otpuri_strlen(OTPData* data, const char* issuer, const char* name, const char* digest); -COTPRESULT otpuri_encode_url(const char* data, size_t length, char* output); -COTPRESULT otpuri_build_uri(OTPData* data, const char* issuer, const char* name, const char* digest, char* output); +#pragma once + +#include "cotp.h" + +size_t otpuri_strlen(OTPData* data, const char* issuer, const char* name, const char* digest); +COTPRESULT otpuri_encode_url(const char* data, size_t length, char* output); +COTPRESULT otpuri_build_uri(OTPData* data, const char* issuer, const char* name, const char* digest, char* output); diff --git a/test/main.c b/test/main.c index c15517c..6b0bd43 100644 --- a/test/main.c +++ b/test/main.c @@ -1,288 +1,359 @@ - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - -#if defined(_WIN32) -# include <sysinfoapi.h> -#elif defined(__linux__) -# include <sys/time.h> -#else -# error "OS not supported." -#endif - -#include <openssl/evp.h> -#include <openssl/hmac.h> - -#include "../cotp.h" -#include "../otpuri.h" - - -static const int32_t SHA1_BYTES = 160 / 8; // 20 -static const int32_t SHA256_BYTES = 256 / 8; // 32 -static const int32_t SHA512_BYTES = 512 / 8; // 64 - - -// byte_secret is unbase32 key -// byte_string is data to be HMAC'd -// returns 0 for failure otherwise the length of the string -int hmac_algo_sha1(const char* byte_secret, const char* byte_string, char* out) -{ - // Output len - unsigned int len = SHA1_BYTES; - - unsigned char* result = HMAC( - EVP_sha1(), // algorithm - (unsigned char*)byte_secret, 10, // key - (unsigned char*)byte_string, 8, // data - (unsigned char*)out, // output - &len // output length - ); - - // Return the HMAC success - return result == 0 ? 0 : len; -} - -int hmac_algo_sha256(const char* byte_secret, const char* byte_string, char* out) -{ - // Output len - unsigned int len = SHA256_BYTES; - - unsigned char* result = HMAC( - EVP_sha256(), // algorithm - (unsigned char*)byte_secret, 10, // key - (unsigned char*)byte_string, 8, // data - (unsigned char*)out, // output - &len // output length - ); - - // Return the HMAC success - return result == 0 ? 0 : len; -} - -int hmac_algo_sha512(const char* byte_secret, const char* byte_string, char* out) -{ - // Output len - unsigned int len = SHA512_BYTES; - - unsigned char* result = HMAC( - EVP_sha512(), // algorithm - (unsigned char*)byte_secret, 10, // key - (unsigned char*)byte_string, 8, // data - (unsigned char*)out, // output - &len // output length - ); - - // Return the HMAC success - return result == 0 ? 0 : len; -} - -uint64_t get_current_time() -{ - uint64_t milliseconds = 0; - -#if defined(_WIN32) - FILETIME fileTime; - GetSystemTimeAsFileTime(&fileTime); - - ULARGE_INTEGER largeInteger; - largeInteger.LowPart = fileTime.dwLowDateTime; - largeInteger.HighPart = fileTime.dwHighDateTime; - - milliseconds = (largeInteger.QuadPart - 116444736000000000ULL) / 10000000ULL; -#elif defined(__linux__) - struct timeval sys_time; - gettimeofday(&sys_time, NULL); - - milliseconds = sys_time.tv_sec; -#endif - - return milliseconds; -} - - - -int main(int argc, char** argv) -{ - //////////////////////////////////////////////////////////////// - // Initialization Stuff // - //////////////////////////////////////////////////////////////// - - const int INTERVAL = 30; - const int DIGITS = 6; - - - // Base32 secret to utilize - const char BASE32_SECRET[] = "JBSWY3DPEHPK3PXP"; // JBSWY3DPEHPK3PXP 3E56263A4A655ED7 - - OTPData odata1; - memset(&odata1, 0, sizeof(OTPData)); - - OTPData odata2; - memset(&odata2, 0, sizeof(OTPData)); - - // Create OTPData struct, which decides the environment - OTPData* tdata = totp_new( - &odata1, - BASE32_SECRET, - hmac_algo_sha1, - get_current_time, - DIGITS, - INTERVAL - ); - - OTPData* hdata = hotp_new( - &odata2, - BASE32_SECRET, - hmac_algo_sha1, - DIGITS, - 0 - ); - - // Dump data members of struct OTPData tdata - printf("\\\\ totp tdata \\\\\n"); - printf("tdata->digits: `%u`\n", tdata->digits); - printf("tdata->interval: `%u`\n", tdata->interval); - printf("tdata->method: `%u`\n", tdata->method); - printf("tdata->algo: `0x%p`\n", tdata->algo); - printf("tdata->time: `0x%p`\n", tdata->time); - printf("tdata->base32_secret: `%s`\n", tdata->base32_secret); - printf("// totp tdata //\n\n"); - - // Dump data members of struct OTPData hdata - printf("\\\\ hotp hdata \\\\\n"); - printf("hdata->digits: `%u`\n", hdata->digits); - printf("hdata->method: `%u`\n", hdata->method); - printf("hdata->algo: `0x%p`\n", hdata->algo); - printf("hdata->base32_secret: `%s`\n", hdata->base32_secret); - printf("hdata->count: `%zu`\n", hdata->count); - printf("// hotp hdata //\n\n"); - - printf("Current Time: `%zu`\n\n", get_current_time()); - - - - //////////////////////////////////////////////////////////////// - // URI Example // - //////////////////////////////////////////////////////////////// - - char name1[] = "name1"; - char name2[] = "name2"; - char whatever1[] = "account@whatever1.com"; - char whatever2[] = "account@whatever2.com"; - - size_t totp_uri_max = otpuri_strlen(tdata, name1, whatever1, "SHA1"); - size_t hotp_uri_max = otpuri_strlen(hdata, name2, whatever2, "SHA1"); - printf("Maximum buffer size for TOTP: `%zu`\n", totp_uri_max); - printf("Maximum buffer size for HOTP: `%zu`\n\n", hotp_uri_max); - - char totp_uri[totp_uri_max + 1]; - memset(totp_uri, 0, totp_uri_max + 1); - otpuri_build_uri(tdata, name1, whatever1, "SHA1", totp_uri); - printf("TOTP URI (%zu bytes): `%s`\n", strlen(totp_uri), totp_uri); - - size_t counter = 52; // for example - hdata->count = counter; - - char hotp_uri[hotp_uri_max + 1]; - memset(hotp_uri, 0, hotp_uri_max + 1); - otpuri_build_uri(hdata, name2, whatever2, "SHA1", hotp_uri); - printf("HOTP URI (%zu bytes): `%s`\n\n", strlen(hotp_uri), hotp_uri); - - - - //////////////////////////////////////////////////////////////// - // BASE32 Stuff // - //////////////////////////////////////////////////////////////// - - const int base32_len = 16; // must be % 8 == 0 - - // Generate random base32 - char base32_new_secret[base32_len + 1]; - memset(&base32_new_secret, 0, base32_len + 1); - - otp_random_base32(base32_len, base32_new_secret); - printf("Random Generated BASE32 Secret: `%s`\n", base32_new_secret); - - puts(""); // line break for readability - - - - //////////////////////////////////////////////////////////////// - // TOTP Stuff // - //////////////////////////////////////////////////////////////// - - // Get TOTP for a timeblock - // 1. Reserve memory and ensure it's null-terminated - // 2. Generate and load totp key into buffer - // 3. Check for error - - // totp_now - char tcode[DIGITS+1]; - memset(tcode, 0, DIGITS+1); - - int totp_err_1 = totp_now(tdata, tcode); - if(totp_err_1 == OTP_ERROR) - { - fputs("TOTP Error totp_now", stderr); - return EXIT_FAILURE; - } - printf("totp_now() pass=1: `%s` `%d`\n", tcode, totp_err_1); - - // totp_at - char tcode2[DIGITS+1]; - memset(tcode2, 0, DIGITS+1); - - int totp_err_2 = totp_at(tdata, 0, 0, tcode2); - if(totp_err_2 == OTP_ERROR) - { - fputs("TOTP Error totp_at", stderr); - return EXIT_FAILURE; - } - printf("totp_at(0, 0) pass=1: `%s` `%d`\n", tcode2, totp_err_2); - - // Do a verification for a hardcoded code - // Won't succeed, this code is for a timeblock far into the past/future - int tv1 = totp_verify(tdata, "358892", get_current_time(), 4); - printf("TOTP Verification 1 pass=false: `%s`\n", tv1 == 0 ? "false" : "true"); - - // Will succeed, timeblock 0 for JBSWY3DPEHPK3PXP == 282760 - int tv2 = totp_verify(tdata, "282760", 0, 4); - printf("TOTP Verification 2 pass=true: `%s`\n", tv2 == 0 ? "false" : "true"); - - puts(""); // line break for readability - - - - //////////////////////////////////////////////////////////////// - // HOTP Stuff // - //////////////////////////////////////////////////////////////// - - // Get HOTP for token 1 - // 1. Reserve memory and ensure it's null-terminated - // 2. Generate and load hotp key into buffer - // 3. Check for error - - char hcode[DIGITS+1]; - memset(hcode, 0, DIGITS+1); - - int hotp_err_1 = hotp_at(hdata, 1, hcode); - if(hotp_err_1 == OTP_ERROR) - { - puts("HOTP Error hotp_at"); - return EXIT_FAILURE; - } - printf("hotp_at(1) pass=1: `%s` `%d`\n", hcode, hotp_err_1); - - // Do a verification for a hardcoded code - // Won't succeed, 1 for JBSWY3DPEHPK3PXP == 996554 - int hv1 = hotp_compare(hdata, "996555", 1); - printf("HOTP Verification 1 pass=false: `%s`\n", hv1 == 0 ? "false" : "true"); - - // Will succeed, 1 for JBSWY3DPEHPK3PXP == 996554 - int hv2 = hotp_compare(hdata, "996554", 1); - printf("HOTP Verification 2 pass=true: `%s`\n", hv2 == 0 ? "false" : "true"); - - return EXIT_SUCCESS; -} - + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#if defined(_WIN32) +# include <sysinfoapi.h> +#elif defined(__linux__) +# include <sys/time.h> +#else +# error "OS not supported." +#endif + +#include <openssl/evp.h> +#include <openssl/hmac.h> + +#include "../cotp.h" +#include "../otpuri.h" + + +static const int32_t SHA1_BYTES = 160 / 8; // 20 +static const int32_t SHA256_BYTES = 256 / 8; // 32 +static const int32_t SHA512_BYTES = 512 / 8; // 64 + + +// byte_secret is unbase32 key +// byte_string is data to be HMAC'd +// returns 0 for failure otherwise the length of the string +int hmac_algo_sha1(const char* byte_secret, int key_length, const char* byte_string, char* out) +{ + // Output len + unsigned int len = SHA1_BYTES; + + unsigned char* result = HMAC( + EVP_sha1(), // algorithm + (unsigned char*)byte_secret, key_length, // key + (unsigned char*)byte_string, 8, // data + (unsigned char*)out, // output + &len // output length + ); + + // Return the HMAC success + return result == 0 ? 0 : len; +} + +int hmac_algo_sha256(const char* byte_secret, int key_length, const char* byte_string, char* out) +{ + // Output len + unsigned int len = SHA256_BYTES; + + unsigned char* result = HMAC( + EVP_sha256(), // algorithm + (unsigned char*)byte_secret, key_length, // key + (unsigned char*)byte_string, 8, // data + (unsigned char*)out, // output + &len // output length + ); + + // Return the HMAC success + return result == 0 ? 0 : len; +} + +int hmac_algo_sha512(const char* byte_secret, int key_length, const char* byte_string, char* out) +{ + // Output len + unsigned int len = SHA512_BYTES; + + unsigned char* result = HMAC( + EVP_sha512(), // algorithm + (unsigned char*)byte_secret, key_length, // key + (unsigned char*)byte_string, 8, // data + (unsigned char*)out, // output + &len // output length + ); + + // Return the HMAC success + return result == 0 ? 0 : len; +} + +uint64_t get_current_time() +{ + uint64_t milliseconds = 0; + +#if defined(_WIN32) + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + + ULARGE_INTEGER largeInteger; + largeInteger.LowPart = fileTime.dwLowDateTime; + largeInteger.HighPart = fileTime.dwHighDateTime; + + milliseconds = (largeInteger.QuadPart - 116444736000000000ULL) / 10000000ULL; +#elif defined(__linux__) + struct timeval sys_time; + gettimeofday(&sys_time, NULL); + + milliseconds = sys_time.tv_sec; +#endif + + return milliseconds; +} + + + +int main(int argc, char** argv) +{ + //////////////////////////////////////////////////////////////// + // Initialization Stuff // + //////////////////////////////////////////////////////////////// + + const int INTERVAL = 30; + const int DIGITS = 6; + + + // Base32 secret to utilize + const char BASE32_SECRET[] = "JBSWY3DPEHPK3PXP"; // JBSWY3DPEHPK3PXP 3E56263A4A655ED7 + + // Base32 secret to utilize with padding + const char BASE32_SECRET_PADDING[] = "ORSXG5BRGIZXIZLTOQ2DKNRXHA4XIZLTOQYQ===="; + + OTPData odata1; + memset(&odata1, 0, sizeof(OTPData)); + + OTPData odata_padding; + memset(&odata_padding, 0, sizeof(OTPData)); + + OTPData odata2; + memset(&odata2, 0, sizeof(OTPData)); + + // Create OTPData struct, which decides the environment + OTPData* tdata = totp_new( + &odata1, + BASE32_SECRET, + hmac_algo_sha1, + get_current_time, + DIGITS, + INTERVAL + ); + + OTPData* tdata_padding = totp_new( + &odata_padding, + BASE32_SECRET_PADDING, + hmac_algo_sha1, + get_current_time, + DIGITS, + INTERVAL + ); + + OTPData* hdata = hotp_new( + &odata2, + BASE32_SECRET, + hmac_algo_sha1, + DIGITS, + 0 + ); + + // Dump data members of struct OTPData tdata + printf("\\\\ totp tdata \\\\\n"); + printf("tdata->digits: `%u`\n", tdata->digits); + printf("tdata->interval: `%u`\n", tdata->interval); + printf("tdata->method: `%u`\n", tdata->method); + printf("tdata->algo: `0x%p`\n", tdata->algo); + printf("tdata->time: `0x%p`\n", tdata->time); + printf("tdata->base32_secret: `%s`\n", tdata->base32_secret); + printf("// totp tdata //\n\n"); + + // Dump data members of struct OTPData tdata_padding + printf("\\\\ totp tdata_padding \\\\\n"); + printf("tdata_padding->digits: `%u`\n", tdata_padding->digits); + printf("tdata_padding->interval: `%u`\n", tdata_padding->interval); + printf("tdata_padding->method: `%u`\n", tdata_padding->method); + printf("tdata_padding->algo: `0x%p`\n", tdata_padding->algo); + printf("tdata_padding->time: `0x%p`\n", tdata_padding->time); + printf("tdata_padding->base32_secret: `%s`\n", tdata_padding->base32_secret); + printf("// totp tdata_padding //\n\n"); + + // Dump data members of struct OTPData hdata + printf("\\\\ hotp hdata \\\\\n"); + printf("hdata->digits: `%u`\n", hdata->digits); + printf("hdata->method: `%u`\n", hdata->method); + printf("hdata->algo: `0x%p`\n", hdata->algo); + printf("hdata->base32_secret: `%s`\n", hdata->base32_secret); + printf("hdata->count: `%zu`\n", hdata->count); + printf("// hotp hdata //\n\n"); + + printf("Current Time: `%zu`\n\n", get_current_time()); + + + + //////////////////////////////////////////////////////////////// + // URI Example // + //////////////////////////////////////////////////////////////// + + char name1[] = "name1"; + char name2[] = "name2"; + char whatever1[] = "account@whatever1.com"; + char whatever2[] = "account@whatever2.com"; + + size_t totp_uri_max = otpuri_strlen(tdata, name1, whatever1, "SHA1"); + size_t hotp_uri_max = otpuri_strlen(hdata, name2, whatever2, "SHA1"); + printf("Maximum buffer size for TOTP: `%zu`\n", totp_uri_max); + printf("Maximum buffer size for HOTP: `%zu`\n\n", hotp_uri_max); + + char totp_uri[totp_uri_max + 1]; + memset(totp_uri, 0, totp_uri_max + 1); + otpuri_build_uri(tdata, name1, whatever1, "SHA1", totp_uri); + printf("TOTP URI (%zu bytes): `%s`\n", strlen(totp_uri), totp_uri); + + size_t counter = 52; // for example + hdata->count = counter; + + char hotp_uri[hotp_uri_max + 1]; + memset(hotp_uri, 0, hotp_uri_max + 1); + otpuri_build_uri(hdata, name2, whatever2, "SHA1", hotp_uri); + printf("HOTP URI (%zu bytes): `%s`\n\n", strlen(hotp_uri), hotp_uri); + + + + //////////////////////////////////////////////////////////////// + // BASE32 Stuff // + //////////////////////////////////////////////////////////////// + + const int base32_len = 16; // must be % 8 == 0 + + // Generate random base32 + char base32_new_secret[base32_len + 1]; + memset(&base32_new_secret, 0, base32_len + 1); + + otp_random_base32(base32_len, base32_new_secret); + printf("Random Generated BASE32 Secret: `%s`\n", base32_new_secret); + + puts(""); // line break for readability + + + + //////////////////////////////////////////////////////////////// + // TOTP Stuff // + //////////////////////////////////////////////////////////////// + + // Get TOTP for a timeblock + // 1. Reserve memory and ensure it's null-terminated + // 2. Generate and load totp key into buffer + // 3. Check for error + + // totp_now + char tcode[DIGITS+1]; + memset(tcode, 0, DIGITS+1); + + int totp_err_1 = totp_now(tdata, tcode); + if(totp_err_1 == OTP_ERROR) + { + fputs("TOTP Error totp_now", stderr); + return EXIT_FAILURE; + } + printf("totp_now() pass=1: `%s` `%d`\n", tcode, totp_err_1); + + // totp_at + char tcode2[DIGITS+1]; + memset(tcode2, 0, DIGITS+1); + + int totp_err_2 = totp_at(tdata, 0, 0, tcode2); + if(totp_err_2 == OTP_ERROR) + { + fputs("TOTP Error totp_at", stderr); + return EXIT_FAILURE; + } + printf("totp_at(0, 0) pass=1: `%s` `%d`\n", tcode2, totp_err_2); + + // Do a verification for a hardcoded code + // Won't succeed, this code is for a timeblock far into the past/future + int tv1 = totp_verify(tdata, "358892", get_current_time(), 4); + printf("TOTP Verification 1 pass=false: `%s`\n", tv1 == 0 ? "false" : "true"); + + // Will succeed, timeblock 0 for JBSWY3DPEHPK3PXP == 282760 + int tv2 = totp_verify(tdata, "282760", 0, 4); + printf("TOTP Verification 2 pass=true: `%s`\n", tv2 == 0 ? "false" : "true"); + + puts(""); // line break for readability + + + + //////////////////////////////////////////////////////////////// + // TOTP Stuff (Padding) // + //////////////////////////////////////////////////////////////// + + // Get TOTP for a timeblock + // 1. Reserve memory and ensure it's null-terminated + // 2. Generate and load totp key into buffer + // 3. Check for error + + // totp_now + char tcode3[DIGITS+1]; + memset(tcode3, 0, DIGITS+1); + + int totp_err_3 = totp_now(tdata_padding, tcode3); + if(totp_err_3 == OTP_ERROR) + { + fputs("TOTP Error totp_now (padding)", stderr); + return EXIT_FAILURE; + } + printf("totp_now() (padding) pass=1: `%s` `%d`\n", tcode3, totp_err_3); + + // totp_at + char tcode4[DIGITS+1]; + memset(tcode4, 0, DIGITS+1); + + int totp_err_4 = totp_at(tdata_padding, 0, 0, tcode4); + if(totp_err_4 == OTP_ERROR) + { + fputs("TOTP Error totp_at (padding)", stderr); + return EXIT_FAILURE; + } + printf("totp_at(0, 0) (padding) pass=1: `%s` `%d`\n", tcode4, totp_err_4); + + // Do a verification for a hardcoded code + // Won't succeed, this code is for a timeblock far into the past/future + int tv3 = totp_verify(tdata_padding, "122924", get_current_time(), 4); + printf("TOTP Verification 1 (padding) pass=false: `%s`\n", tv3 == 0 ? "false" : "true"); + + // Will succeed, timeblock 0 for 'ORSXG5BRGIZXIZLTOQ2DKNRXHA4XIZLTOQYQ====' == 570783 + int tv4 = totp_verify(tdata_padding, "570783", 0, 4); + printf("TOTP Verification 2 (padding) pass=true: `%s`\n", tv4 == 0 ? "false" : "true"); + + puts(""); // line break for readability + + + + //////////////////////////////////////////////////////////////// + // HOTP Stuff // + //////////////////////////////////////////////////////////////// + + // Get HOTP for token 1 + // 1. Reserve memory and ensure it's null-terminated + // 2. Generate and load hotp key into buffer + // 3. Check for error + + char hcode[DIGITS+1]; + memset(hcode, 0, DIGITS+1); + + int hotp_err_1 = hotp_at(hdata, 1, hcode); + if(hotp_err_1 == OTP_ERROR) + { + puts("HOTP Error hotp_at"); + return EXIT_FAILURE; + } + printf("hotp_at(1) pass=1: `%s` `%d`\n", hcode, hotp_err_1); + + // Do a verification for a hardcoded code + // Won't succeed, 1 for JBSWY3DPEHPK3PXP == 996554 + int hv1 = hotp_compare(hdata, "996555", 1); + printf("HOTP Verification 1 pass=false: `%s`\n", hv1 == 0 ? "false" : "true"); + + // Will succeed, 1 for JBSWY3DPEHPK3PXP == 996554 + int hv2 = hotp_compare(hdata, "996554", 1); + printf("HOTP Verification 2 pass=true: `%s`\n", hv2 == 0 ? "false" : "true"); + + return EXIT_SUCCESS; +} + diff --git a/test/main.cpp b/test/main.cpp index 66b4ff7..3c7d785 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,279 +1,353 @@ - -#include <cstdlib> -#include <iostream> -#include <cstring> -#include <chrono> - -#include "../cotp.hpp" - -extern "C" -{ - #include <openssl/evp.h> - #include <openssl/hmac.h> -} - - -using namespace std; -using namespace COTP; - - -static const int32_t SHA1_BYTES = 160 / 8; // 20 -static const int32_t SHA256_BYTES = 256 / 8; // 32 -static const int32_t SHA512_BYTES = 512 / 8; // 64 - - -// byte_secret is unbase32 key -// byte_string is data to be HMAC'd -// returns 0 for failure otherwise the length of the string -int hmac_algo_sha1(const char* byte_secret, const char* byte_string, char* out) -{ - // Output len - unsigned int len = SHA1_BYTES; - - unsigned char* result = HMAC( - EVP_sha1(), // algorithm - (unsigned char*)byte_secret, 10, // key - (unsigned char*)byte_string, 8, // data - (unsigned char*)out, // output - &len // output length - ); - - // Return the HMAC success - return result == 0 ? 0 : len; -} - - -int hmac_algo_sha256(const char* byte_secret, const char* byte_string, char* out) -{ - // Output len - unsigned int len = SHA256_BYTES; - - unsigned char* result = HMAC( - EVP_sha256(), // algorithm - (unsigned char*)byte_secret, 10, // key - (unsigned char*)byte_string, 8, // data - (unsigned char*)out, // output - &len // output length - ); - - // Return the HMAC success - return result == 0 ? 0 : len; -} - -int hmac_algo_sha512(const char* byte_secret, const char* byte_string, char* out) -{ - // Output len - unsigned int len = SHA512_BYTES; - - unsigned char* result = HMAC( - EVP_sha512(), // algorithm - (unsigned char*)byte_secret, 10, // key - (unsigned char*)byte_string, 8, // data - (unsigned char*)out, // output - &len // output length - ); - - // Return the HMAC success - return result == 0 ? 0 : len; -} - -// TODO: use a secure random generator -uint64_t get_current_time() -{ - using namespace std::chrono; - - auto now = system_clock::now(); - auto dur = now.time_since_epoch(); - - return duration_cast<chrono::seconds>(dur).count(); -} - - - -int main(int argc, char** argv) -{ - //////////////////////////////////////////////////////////////// - // Initialization Stuff // - //////////////////////////////////////////////////////////////// - - const int INTERVAL = 30; - const int DIGITS = 6; - - - // Base32 secret to utilize - const char BASE32_SECRET[] = "JBSWY3DPEHPK3PXP"; - - OTPData odata1; - memset(&odata1, 0, sizeof(OTPData)); - - OTPData odata2; - memset(&odata2, 0, sizeof(OTPData)); - - // Create OTPData struct, which decides the environment - class TOTP tdata - { - &odata1, - BASE32_SECRET, - hmac_algo_sha1, - get_current_time, - DIGITS, - INTERVAL - }; - - class HOTP hdata - { - &odata2, - BASE32_SECRET, - hmac_algo_sha1, - DIGITS, - 0 - }; - - // Dump data members of struct OTPData tdata - OTPData* tdata_s = tdata.data_struct(); - cout << "\\\\ totp tdata \\\\" << endl; - cout << "tdata->digits: `" << tdata_s->digits << "`" << endl; - cout << "tdata->interval: `" << tdata_s->interval << "`" << endl; - cout << "tdata->method: `" << tdata_s->method << "`" << endl; - cout << "tdata->algo: `" << reinterpret_cast<void*>(tdata_s->algo) << "`" << endl; - cout << "tdata->time: `" << reinterpret_cast<void*>(tdata_s->time) << "`" << endl; - cout << "tdata->base32_secret: `" << tdata_s->base32_secret << "`" << endl; - cout << "// totp tdata //" << endl << endl; - - // Dump data members of struct OTPData hdata - OTPData* hdata_s = hdata.data_struct(); - cout << "\\\\ hotp hdata \\\\" << endl; - cout << "hdata->digits: `" << hdata_s->digits << "`" << endl; - cout << "hdata->method: `" << hdata_s->method << "`" << endl; - cout << "hdata->algo: `" << reinterpret_cast<void*>(hdata_s->algo) << "`" << endl; - cout << "hdata->base32_secret: `" << hdata_s->base32_secret << "`" << endl; - cout << "hdata->count: `" << hdata_s->count << "`" << endl; - cout << "// hotp hdata //" << endl << endl; - - cout << "Current Time: `" << get_current_time() << "`" << endl << endl; - - - - //////////////////////////////////////////////////////////////// - // URI Example // - //////////////////////////////////////////////////////////////// - - char name1[] = "name1"; - char name2[] = "name2"; - char whatever1[] = "account@whatever1.com"; - char whatever2[] = "account@whatever2.com"; - - size_t totp_uri_max = tdata.uri_strlen(name1, whatever1, "SHA1"); - size_t hotp_uri_max = hdata.uri_strlen(name2, whatever2, "SHA1"); - cout << "Maximum buffer size for TOTP: `" << totp_uri_max << "`" << endl; - cout << "Maximum buffer size for HOTP: `" << hotp_uri_max << "`" << endl << endl; - - char totp_uri[totp_uri_max + 1]; - memset(totp_uri, 0, totp_uri_max + 1); - tdata.build_uri(name1, whatever1, "SHA1", totp_uri); - cout << "TOTP URI: `" << totp_uri << "`" << endl; - - size_t counter = 52; // for example - hdata_s->count = counter; - - char hotp_uri[hotp_uri_max + 1]; - memset(hotp_uri, 0, hotp_uri_max + 1); - hdata.build_uri(name2, whatever2, "SHA1", hotp_uri); - cout << "HOTP URI: `" << hotp_uri << "`" << endl << endl; - - - - //////////////////////////////////////////////////////////////// - // BASE32 Stuff // - //////////////////////////////////////////////////////////////// - - const int base32_len = 16; // must be % 8 == 0 - - // Generate random base32 - char base32_new_secret[base32_len + 1]; - memset(&base32_new_secret, 0, base32_len + 1); - - OTP::random_base32(base32_len, base32_new_secret); - cout << "Random Generated BASE32 Secret: `" << base32_new_secret << "`" << endl; - - cout << endl; // line break for readability - - - - //////////////////////////////////////////////////////////////// - // TOTP Stuff // - //////////////////////////////////////////////////////////////// - - // Get TOTP for a timeblock - // 1. Reserve memory and ensure it's null-terminated - // 2. Generate and load totp key into buffer - // 3. Check for error - - // totp_now - char tcode[DIGITS+1]; - memset(tcode, 0, DIGITS+1); - - int totp_err_1 = tdata.now(tcode); - if(totp_err_1 == OTP_ERROR) - { - cout << "TOTP Error totp_now" << endl; - return EXIT_FAILURE; - } - cout << "totp_now() pass=1: `" << tcode << "` `" << totp_err_1 << "`" << endl; - - // totp_at - char tcode2[DIGITS+1]; - memset(tcode2, 0, DIGITS+1); - - int totp_err_2 = tdata.at(0, 0, tcode2); - if(totp_err_2 == 0) - { - cout << "TOTP Error totp_at" << endl; - return EXIT_FAILURE; - } - cout << "totp_at(0, 0) pass=1: `" << tcode2 << "` `" << totp_err_2 << "`" << endl; - - // Do a verification for a hardcoded code - // Won't succeed, this code is for a timeblock far into the past/future - int tv1 = tdata.verify("358892", get_current_time(), 4); - cout << "TOTP Verification 1 pass=false: `" << (tv1 == 0 ? "false" : "true") << "`" << endl; - - // Will succeed, timeblock 0 for JBSWY3DPEHPK3PXP == 282760 - int tv2 = tdata.verify("282760", 0, 4); - cout << "TOTP Verification 2 pass=true: `" << (tv2 == 0 ? "false" : "true") << "`" << endl; - - cout << endl; // line break for readability - - - - //////////////////////////////////////////////////////////////// - // HOTP Stuff // - //////////////////////////////////////////////////////////////// - - // Get HOTP for token 1 - // 1. Reserve memory and ensure it's null-terminated - // 2. Generate and load hotp key into buffer - // 3. Check for error - - char hcode[DIGITS+1]; - memset(hcode, 0, DIGITS+1); - - int hotp_err_1 = hdata.at(1, hcode); - if(hotp_err_1 == 0) { - cout << "HOTP Error hotp_at" << endl; - return EXIT_FAILURE; - } - cout << "hotp_at(1) pass=1: `" << hcode << "`" << " `" << hotp_err_1 << "`" << endl; - - // Do a verification for a hardcoded code - // Won't succeed, 1 for JBSWY3DPEHPK3PXP == 996554 - int hv1 = hdata.compare("996555", 1); - cout << "HOTP Verification 1 pass=false: `" << (hv1 == 0 ? "false" : "true") << "`" << endl; - - // Will succeed, 1 for JBSWY3DPEHPK3PXP == 996554 - int hv2 = hdata.compare("996554", 1); - cout << "HOTP Verification 2 pass=true: `" << (hv2 == 0 ? "false" : "true") << "`" << endl; - - return EXIT_SUCCESS; -} - + +#include <cstdlib> +#include <iostream> +#include <cstring> +#include <chrono> + +#include "../cotp.hpp" + +extern "C" +{ + #include <openssl/evp.h> + #include <openssl/hmac.h> +} + + +using namespace std; +using namespace COTP; + + +static const int32_t SHA1_BYTES = 160 / 8; // 20 +static const int32_t SHA256_BYTES = 256 / 8; // 32 +static const int32_t SHA512_BYTES = 512 / 8; // 64 + + +// byte_secret is unbase32 key +// byte_string is data to be HMAC'd +// returns 0 for failure otherwise the length of the string +int hmac_algo_sha1(const char* byte_secret, int key_length, const char* byte_string, char* out) +{ + // Output len + unsigned int len = SHA1_BYTES; + + unsigned char* result = HMAC( + EVP_sha1(), // algorithm + (unsigned char*)byte_secret, key_length, // key + (unsigned char*)byte_string, 8, // data + (unsigned char*)out, // output + &len // output length + ); + + // Return the HMAC success + return result == 0 ? 0 : len; +} + + +int hmac_algo_sha256(const char* byte_secret, int key_length, const char* byte_string, char* out) +{ + // Output len + unsigned int len = SHA256_BYTES; + + unsigned char* result = HMAC( + EVP_sha256(), // algorithm + (unsigned char*)byte_secret, key_length, // key + (unsigned char*)byte_string, 8, // data + (unsigned char*)out, // output + &len // output length + ); + + // Return the HMAC success + return result == 0 ? 0 : len; +} + +int hmac_algo_sha512(const char* byte_secret, int key_length, const char* byte_string, char* out) +{ + // Output len + unsigned int len = SHA512_BYTES; + + unsigned char* result = HMAC( + EVP_sha512(), // algorithm + (unsigned char*)byte_secret, key_length, // key + (unsigned char*)byte_string, 8, // data + (unsigned char*)out, // output + &len // output length + ); + + // Return the HMAC success + return result == 0 ? 0 : len; +} + +// TODO: use a secure random generator +uint64_t get_current_time() +{ + using namespace std::chrono; + + auto now = system_clock::now(); + auto dur = now.time_since_epoch(); + + return duration_cast<chrono::seconds>(dur).count(); +} + + + +int main(int argc, char** argv) +{ + //////////////////////////////////////////////////////////////// + // Initialization Stuff // + //////////////////////////////////////////////////////////////// + + const int INTERVAL = 30; + const int DIGITS = 6; + + + // Base32 secret to utilize + const char BASE32_SECRET[] = "JBSWY3DPEHPK3PXP"; + + // Base32 secret to utilize with padding + const char BASE32_SECRET_PADDING[] = "ORSXG5BRGIZXIZLTOQ2DKNRXHA4XIZLTOQYQ===="; + + OTPData odata1; + memset(&odata1, 0, sizeof(OTPData)); + + OTPData odata_padding; + memset(&odata_padding, 0, sizeof(OTPData)); + + OTPData odata2; + memset(&odata2, 0, sizeof(OTPData)); + + // Create OTPData struct, which decides the environment + class TOTP tdata + { + &odata1, + BASE32_SECRET, + hmac_algo_sha1, + get_current_time, + DIGITS, + INTERVAL + }; + + class TOTP tdata_padding + { + &odata_padding, + BASE32_SECRET_PADDING, + hmac_algo_sha1, + get_current_time, + DIGITS, + INTERVAL + }; + + class HOTP hdata + { + &odata2, + BASE32_SECRET, + hmac_algo_sha1, + DIGITS, + 0 + }; + + // Dump data members of struct OTPData tdata + OTPData* tdata_s = tdata.data_struct(); + cout << "\\\\ totp tdata \\\\" << endl; + cout << "tdata->digits: `" << tdata_s->digits << "`" << endl; + cout << "tdata->interval: `" << tdata_s->interval << "`" << endl; + cout << "tdata->method: `" << tdata_s->method << "`" << endl; + cout << "tdata->algo: `" << reinterpret_cast<void*>(tdata_s->algo) << "`" << endl; + cout << "tdata->time: `" << reinterpret_cast<void*>(tdata_s->time) << "`" << endl; + cout << "tdata->base32_secret: `" << tdata_s->base32_secret << "`" << endl; + cout << "// totp tdata //" << endl << endl; + + // Dump data members of struct OTPData tdata_padding + OTPData* tdata_padding_s = tdata.data_struct(); + cout << "\\\\ totp tdata_padding \\\\" << endl; + cout << "tdata_padding->digits: `" << tdata_padding_s->digits << "`" << endl; + cout << "tdata_padding->interval: `" << tdata_padding_s->interval << "`" << endl; + cout << "tdata_padding->method: `" << tdata_padding_s->method << "`" << endl; + cout << "tdata_padding->algo: `" << reinterpret_cast<void*>(tdata_padding_s->algo) << "`" << endl; + cout << "tdata_padding->time: `" << reinterpret_cast<void*>(tdata_padding_s->time) << "`" << endl; + cout << "tdata_padding->base32_secret: `" << tdata_padding_s->base32_secret << "`" << endl; + cout << "// totp tdata_padding //" << endl << endl; + + + // Dump data members of struct OTPData hdata + OTPData* hdata_s = hdata.data_struct(); + cout << "\\\\ hotp hdata \\\\" << endl; + cout << "hdata->digits: `" << hdata_s->digits << "`" << endl; + cout << "hdata->method: `" << hdata_s->method << "`" << endl; + cout << "hdata->algo: `" << reinterpret_cast<void*>(hdata_s->algo) << "`" << endl; + cout << "hdata->base32_secret: `" << hdata_s->base32_secret << "`" << endl; + cout << "hdata->count: `" << hdata_s->count << "`" << endl; + cout << "// hotp hdata //" << endl << endl; + + cout << "Current Time: `" << get_current_time() << "`" << endl << endl; + + + + //////////////////////////////////////////////////////////////// + // URI Example // + //////////////////////////////////////////////////////////////// + + char name1[] = "name1"; + char name2[] = "name2"; + char whatever1[] = "account@whatever1.com"; + char whatever2[] = "account@whatever2.com"; + + size_t totp_uri_max = tdata.uri_strlen(name1, whatever1, "SHA1"); + size_t hotp_uri_max = hdata.uri_strlen(name2, whatever2, "SHA1"); + cout << "Maximum buffer size for TOTP: `" << totp_uri_max << "`" << endl; + cout << "Maximum buffer size for HOTP: `" << hotp_uri_max << "`" << endl << endl; + + char totp_uri[totp_uri_max + 1]; + memset(totp_uri, 0, totp_uri_max + 1); + tdata.build_uri(name1, whatever1, "SHA1", totp_uri); + cout << "TOTP URI: `" << totp_uri << "`" << endl; + + size_t counter = 52; // for example + hdata_s->count = counter; + + char hotp_uri[hotp_uri_max + 1]; + memset(hotp_uri, 0, hotp_uri_max + 1); + hdata.build_uri(name2, whatever2, "SHA1", hotp_uri); + cout << "HOTP URI: `" << hotp_uri << "`" << endl << endl; + + + + //////////////////////////////////////////////////////////////// + // BASE32 Stuff // + //////////////////////////////////////////////////////////////// + + const int base32_len = 16; // must be % 8 == 0 + + // Generate random base32 + char base32_new_secret[base32_len + 1]; + memset(&base32_new_secret, 0, base32_len + 1); + + OTP::random_base32(base32_len, base32_new_secret); + cout << "Random Generated BASE32 Secret: `" << base32_new_secret << "`" << endl; + + cout << endl; // line break for readability + + + + //////////////////////////////////////////////////////////////// + // TOTP Stuff // + //////////////////////////////////////////////////////////////// + + // Get TOTP for a timeblock + // 1. Reserve memory and ensure it's null-terminated + // 2. Generate and load totp key into buffer + // 3. Check for error + + // totp_now + char tcode[DIGITS+1]; + memset(tcode, 0, DIGITS+1); + + int totp_err_1 = tdata.now(tcode); + if(totp_err_1 == OTP_ERROR) + { + cout << "TOTP Error totp_now (padding)" << endl; + return EXIT_FAILURE; + } + cout << "totp_now() (padding) pass=1: `" << tcode << "` `" << totp_err_1 << "`" << endl; + + // totp_at + char tcode2[DIGITS+1]; + memset(tcode2, 0, DIGITS+1); + + int totp_err_2 = tdata.at(0, 0, tcode2); + if(totp_err_2 == 0) + { + cout << "TOTP Error totp_at (padding)" << endl; + return EXIT_FAILURE; + } + cout << "totp_at(0, 0) (padding) pass=1: `" << tcode2 << "` `" << totp_err_2 << "`" << endl; + + // Do a verification for a hardcoded code + // Won't succeed, this code is for a timeblock far into the past/future + int tv1 = tdata.verify("358892", get_current_time(), 4); + cout << "TOTP Verification 1 (padding) pass=false: `" << (tv1 == 0 ? "false" : "true") << "`" << endl; + + // Will succeed, timeblock 0 for JBSWY3DPEHPK3PXP == 282760 + int tv2 = tdata.verify("282760", 0, 4); + cout << "TOTP Verification 2 (padding) pass=true: `" << (tv2 == 0 ? "false" : "true") << "`" << endl; + + cout << endl; // line break for readability + + + + //////////////////////////////////////////////////////////////// + // TOTP Stuff (Padding) // + //////////////////////////////////////////////////////////////// + + // Get TOTP for a timeblock + // 1. Reserve memory and ensure it's null-terminated + // 2. Generate and load totp key into buffer + // 3. Check for error + + // totp_now + char tcode3[DIGITS+1]; + memset(tcode3, 0, DIGITS+1); + + int totp_err_3 = tdata_padding.now(tcode3); + if(totp_err_3 == OTP_ERROR) + { + cout << "TOTP Error totp_now" << endl; + return EXIT_FAILURE; + } + cout << "totp_now() pass=1: `" << tcode3 << "` `" << totp_err_3 << "`" << endl; + + // totp_at + char tcode4[DIGITS+1]; + memset(tcode4, 0, DIGITS+1); + + int totp_err_4 = tdata_padding.at(0, 0, tcode4); + if(totp_err_4 == 0) + { + cout << "TOTP Error totp_at" << endl; + return EXIT_FAILURE; + } + cout << "totp_at(0, 0) pass=1: `" << tcode4 << "` `" << totp_err_4 << "`" << endl; + + // Do a verification for a hardcoded code + // Won't succeed, this code is for a timeblock far into the past/future + int tv3 = tdata_padding.verify("358892", get_current_time(), 4); + cout << "TOTP Verification 1 pass=false: `" << (tv3 == 0 ? "false" : "true") << "`" << endl; + + // Will succeed, timeblock 0 for 'ORSXG5BRGIZXIZLTOQ2DKNRXHA4XIZLTOQYQ====' == 570783 + int tv4 = tdata_padding.verify("570783", 0, 4); + cout << "TOTP Verification 2 pass=true: `" << (tv4 == 0 ? "false" : "true") << "`" << endl; + + cout << endl; // line break for readability + + + + //////////////////////////////////////////////////////////////// + // HOTP Stuff // + //////////////////////////////////////////////////////////////// + + // Get HOTP for token 1 + // 1. Reserve memory and ensure it's null-terminated + // 2. Generate and load hotp key into buffer + // 3. Check for error + + char hcode[DIGITS+1]; + memset(hcode, 0, DIGITS+1); + + int hotp_err_1 = hdata.at(1, hcode); + if(hotp_err_1 == 0) { + cout << "HOTP Error hotp_at" << endl; + return EXIT_FAILURE; + } + cout << "hotp_at(1) pass=1: `" << hcode << "`" << " `" << hotp_err_1 << "`" << endl; + + // Do a verification for a hardcoded code + // Won't succeed, 1 for JBSWY3DPEHPK3PXP == 996554 + int hv1 = hdata.compare("996555", 1); + cout << "HOTP Verification 1 pass=false: `" << (hv1 == 0 ? "false" : "true") << "`" << endl; + + // Will succeed, 1 for JBSWY3DPEHPK3PXP == 996554 + int hv2 = hdata.compare("996554", 1); + cout << "HOTP Verification 2 pass=true: `" << (hv2 == 0 ? "false" : "true") << "`" << endl; + + return EXIT_SUCCESS; +} +