From 7ec4cca908fbcc837fc8fc9b582694c0ac6c90f4 Mon Sep 17 00:00:00 2001 From: Hizenberg Date: Sat, 11 May 2024 23:26:56 +0530 Subject: [PATCH] Working model finished --- .gitignore | 5 + CMakeLists.txt | 136 ++++++++++++++++++++++ app/CMakeLists.txt | 8 ++ app/main.c | 217 ++++++++++++++++++++++++++++++++++++ cmake/AddGitSubmodule.cmake | 39 +++++++ include/rfc4226.h | 40 +++++++ include/rfc6238.h | 16 +++ src/CMakeLists.txt | 11 ++ src/rfc4226.c | 91 +++++++++++++++ src/rfc6238.c | 17 +++ 10 files changed, 580 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 app/CMakeLists.txt create mode 100644 app/main.c create mode 100644 cmake/AddGitSubmodule.cmake create mode 100644 include/rfc4226.h create mode 100644 include/rfc6238.h create mode 100644 src/CMakeLists.txt create mode 100644 src/rfc4226.c create mode 100644 src/rfc6238.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c5f911 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*vsidx +*.opendb +.vs +out +CMakePresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d5b91d8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,136 @@ +# CMakeList.txt : CMake project for HOTP, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +project(HOTP VERSION 1.0.0 LANGUAGES C CXX) + +set(CMAKE_C_STANDARD 17) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +set(OTP_LIBRARY otp_lib) + +set(OTP_EXE genotp) + + +set(HEADER_DIR "${CMAKE_SOURCE_DIR}/include") +set(EXTERNAL_DIR "${CMAKE_SOURCE_DIR}/external") +set(SOURCE_DIR "${CMAKE_SOURCE_DIR}/src") + +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") +include(AddGitSubmodule) + +add_git_submodule(external openssl https://github.com/openssl/openssl.git) + +set(OPENSSL_PATH "${EXTERNAL_DIR}/openssl") + +if (NOT EXISTS "${OPENSSL_PATH}/CMakeLists.txt") + + include(ExternalProject) + + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + + # Add the directory containing Perl to the CMAKE_PREFIX_PATH variable + list(APPEND CMAKE_PREFIX_PATH "E://Strawberry_Perl//perl//bin") + find_package(Perl) + + if(${PERL_FOUND}) + message(STATUS "Found perl: ${PERL_EXECUTABLE}") + else() + message(FATAL_ERROR "Perl executable not found.") + endif() + + set(CONFIGURE_COMMAND ${PERL_EXECUTABLE} Configure --prefix=${OPENSSL_PATH}/build --openssldir=${OPENSSL_PATH}/build/ssl ) + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(CONFIGURE_COMMAND ./Configure --prefix=${OPENSSL_PATH}/build --openssldir=${OPENSSL_PATH}/build/ssl ) + + endif() + + find_program(MAKE_EXE NAMES nmake make) + + if(MAKE_EXE) + message(STATUS "Found platform specific make executable: ${MAKE_EXE}") + else() + message(FATAL_ERROR "nmake not found. Please make sure it is installed.") + endif() + + + if(NOT EXISTS "${OPENSSL_PATH}/build") + + execute_process( + COMMAND ${CONFIGURE_COMMAND} + WORKING_DIRECTORY ${OPENSSL_PATH} + RESULT_VARIABLE result + ) + + + if(result) + message(FATAL_ERROR "Configuration Command failed.") + endif() + + execute_process( + COMMAND ${MAKE_EXE} + WORKING_DIRECTORY ${OPENSSL_PATH} + RESULT_VARIABLE result + ) + + if(result) + message(FATAL_ERROR "make Command failed.") + endif() + + set(MAKE_EXE_INSTALL ${MAKE_EXE} install) + + execute_process( + COMMAND ${MAKE_EXE_INSTALL} + WORKING_DIRECTORY ${OPENSSL_PATH} + RESULT_VARIABLE result + ) + + if(result) + message(FATAL_ERROR "make install Command failed.") + endif() + + endif() + + #This will be executed during build time not CMake Time. + #ExternalProject_Add( + # OpenSSL_deps + # PREFIX ${CMAKE_BINARY_DIR}/external + # DOWNLOAD_COMMAND "" # No download command since we're not downloading, assuming Makefile is already present + # SOURCE_DIR ${OPENSSL_PATH} # Specify the path to the directory containing the Makefile + # BINARY_DIR ${OPENSSL_PATH}/lib #All the build file will be present here, if BUILD_IN_SOURCE is not set. + # CONFIGURE_COMMAND ${CONFIGURE_COMMAND} # No configure command needed for Makefile-based projects + # BUILD_COMMAND ${MAKE_EXE} # Command to build the project using make + # INSTALL_COMMAND "" # No install command needed for Makefile-based projects + # BUILD_IN_SOURCE 0 # Build the project in the source directory + #) + +endif() + + +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL PATHS ${OPENSSL_PATH}) + +if( OPENSSL_FOUND ) + message(STATUS "OpenSSL found: ${OPENSSL_INCLUDE_DIR}") + message(STATUS "OpenSSL found: ${OPENSSL_CRYPTO_LIBRARY}") + message(STATUS "OpenSSL found: ${OPENSSL_SSL_LIBRARY}") + message(STATUS "OpenSSL found: ${OPENSSL_LIBRARIES}") +else() + message(FATAL_ERROR "OpenSSL not found") +endif() + +set(OPENSSL_LIB + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${OPENSSL_LIBRARIES}) + +set(OPENSSL_HEADER + ${OPENSSL_INCLUDE_DIR}) + +add_subdirectory(src) +add_subdirectory(app) + +# TODO: Add tests and install targets if needed. diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..dae1038 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(${OTP_EXE} + "main.c") + +target_include_directories(${OTP_EXE} PUBLIC + ${HEADER_DIRS}) + +target_link_libraries(${OTP_EXE} PUBLIC + ${OTP_LIBRARY}) \ No newline at end of file diff --git a/app/main.c b/app/main.c new file mode 100644 index 0000000..c929506 --- /dev/null +++ b/app/main.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include "rfc6238.h" + +#define T0 0 +#define DIGITS 6 +#define VALIDITY 30 +#define TIME 2 + + +/*******************************Code Taken from elsewhere********************************/ + +char b32_decode_char(char c); +void b32_decode(char** dst, size_t* dstlen, const char* src, size_t srclen); + +void b32_decode(char** dst, size_t* dstlen, const char* src, size_t srclen) +{ + size_t padlen = 0; // Number of ='s in padding + size_t lastlen = 0; // Length of last quantum in characters + + *dst = NULL; + *dstlen = 0; + + // Check padding + for (size_t i = 1; i < srclen; i++) + { + if (src[srclen - i] == '=') + padlen++; + else + break; + } + + // Check source material + for (size_t i = 0; i < srclen - padlen; i++) + { + if (b32_decode_char(src[i]) > 0x1F) + { + // ERROR: one or more characters cannot be decoded + return; + } + } + + // Calculate the length of the last quantum in src + lastlen = (srclen - padlen) % 8; + + // How many quantums do we have? + size_t qmax = (srclen - padlen) / 8; + + if (lastlen > 0) + { + // Last quantum is a partial quantum + // ... qmax rounded down + qmax += 1; + } + else + { + // Last quantum is a full quantum + // ... length of last quantum is 8, not 0 + lastlen = 8; + } + + // Calculate dst buffer size + *dstlen = ((srclen - padlen) / 8) * 5; + + switch (lastlen) + { + case 8: + break; + case 7: + *dstlen += 4; + break; + case 5: + *dstlen += 3; + break; + case 4: + *dstlen += 2; + break; + case 2: + *dstlen += 1; + break; + default: + // ERROR: Not a multiple of a byte. + *dstlen = 0; + break; + } + + if (dstlen == 0) + { + // Either empty src, or an error occurred + return; + } + + // Allocate dst buffer + *dst = (char*)malloc(sizeof(char) * (*dstlen)); + + // Loop variables + size_t qlen; + char* pdst = *dst; + const char* psrc = src; + + // Decode each quantum + for (size_t q = 0; q < qmax; q++) + { + // Are we on the last quantum? + if (q == qmax - 1) + qlen = lastlen; + else + qlen = 8; + + // dst 0 1 2 3 4 + // [11111 111][11 11111 1][1111 1111][1 11111 11][111 11111] + // src 0 1 2 3 4 5 6 7 + + switch (qlen) + { + // 8 = 5 bytes in quantum + case 8: + pdst[4] = b32_decode_char(psrc[7]); + pdst[4] |= b32_decode_char(psrc[6]) << 5; + // 7 = 4 bytes in quantum + case 7: + pdst[3] = b32_decode_char(psrc[6]) >> 3; + pdst[3] |= (b32_decode_char(psrc[5]) & 0x1F) << 2; + pdst[3] |= b32_decode_char(psrc[4]) << 7; + // 5 = 3 bytes in quantum + case 5: + pdst[2] = b32_decode_char(psrc[4]) >> 1; + pdst[2] |= b32_decode_char(psrc[3]) << 4; + // 4 = 2 bytes in quantum + case 4: + pdst[1] = b32_decode_char(psrc[3]) >> 4; + pdst[1] |= (b32_decode_char(psrc[2]) & 0x1F) << 1; + pdst[1] |= b32_decode_char(psrc[1]) << 6; + // 2 = 1 byte in quantum + case 2: + pdst[0] = b32_decode_char(psrc[1]) >> 2; + pdst[0] |= b32_decode_char(psrc[0]) << 3; + break; + default: + break; // TODO error + } + + // Move quantum pointers forward + psrc += 8; + pdst += 5; + } +} + +char b32_decode_char(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A'; + else if (c >= '2' && c <= '7') + return c - '2' + 26; + // ... handle lowercase here??? + else if (c >= 'a' && c <= 'z') + return c - 'a'; + else + return 0xFF; // ERROR +} + +/*******************************Code Taken from elsewhere********************************/ + + +int main(int argc, char* argv[]) { + + + char key[50]; + char* k; + char* decodeKey; + size_t sz_dKey; + uint32_t result; + + if (argc <= 1) { + printf("Please provide your secret key!!!!!\n"); + return 0; + } + + unsigned int sz_key = strlen(argv[1]); + + if (sz_key > 50) { + printf("base-32 secret key cannot be more than 50 characters.\n"); + return 0; + } + + + //validate the key... + for (int i = 0; i < sz_key; i++) { + + if ((argv[1][i] < 'A' && argv[1][i] > 'Z') || + (argv[1][i] < '2' && argv[1][i] > '9') || + (argv[1][i] == '=')) { + + printf("Invalid base-32 encoded key.\nPlease enter correct base-32 encoded key.\n"); + return 0; + } + } + + + strncpy(key, argv[1], sz_key); + + + k = key; + + + b32_decode(&decodeKey, &sz_dKey, k, (size_t)sz_key); + + time_t t = floor((time(NULL) - T0) / VALIDITY); + + result = TOTP((uint8_t*)decodeKey, sz_dKey, (uint64_t)t, DIGITS); + + printf("The resulting OTP value is : %06u\n", result); + + return 0; +} \ No newline at end of file diff --git a/cmake/AddGitSubmodule.cmake b/cmake/AddGitSubmodule.cmake new file mode 100644 index 0000000..e443aa9 --- /dev/null +++ b/cmake/AddGitSubmodule.cmake @@ -0,0 +1,39 @@ +function(add_git_submodule relative_dir lib link) + find_package(Git REQUIRED) + + set(FULL_DIR ${CMAKE_SOURCE_DIR}/${relative_dir}) + + #For intializing git if not already initialized. + if(NOT EXISTS "${CMAKE_SOURCE_DIR}/.git") + # If .git directory does not exist, execute git init + execute_process( + COMMAND git init + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE result + ) + + if(result) + message(FATAL_ERROR "Failed to initialize git repository.") + endif() + endif() + + #For adding the external deps if not added yet + if (NOT EXISTS ${FULL_DIR}/${lib}) + execute_process( + COMMAND ${GIT_EXECUTABLE} + submodule add -- ${link} + WORKING_DIRECTORY ${FULL_DIR} + ) + elseif (NOT EXISTS ${FULL_DIR}/CMakeLists.txt) + execute_process(COMMAND ${GIT_EXECUTABLE} + submodule update --init -- ${relative_dir} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) + endif() + + if (EXISTS ${FULL_DIR}/CMakeLists.txt) + message("Submodule is CMake Project: ${FULL_DIR}/CMakeLists.txt") + add_subdirectory(${FULL_DIR}) + else() + message("Submodule is NO CMake Project: ${FULL_DIR}") + endif() +endfunction(add_git_submodule) \ No newline at end of file diff --git a/include/rfc4226.h b/include/rfc4226.h new file mode 100644 index 0000000..7cd33e1 --- /dev/null +++ b/include/rfc4226.h @@ -0,0 +1,40 @@ +#ifndef RFC4226_H +#define RFC4226_H + + +#include +#include + +/* +* Interface API for this library. +*/ + +uint32_t HOTP(uint8_t* key, size_t kl, uint64_t interval, int digits); + +/* +* Step 1 -> Calling hmac function from openssl +* library. It is used to create a 160-bit +* integer value which is processed further. +* It take a string key as an argument and +* a seed value for calculation. +* ->unsigned char* key -> key string +* ->uint64_t interval -> seed value +* | +* | +* -------> This seed value can be integer counter or +* timer value as an argument. +*/ + +uint8_t* hmac(unsigned char* key, int kl, uint64_t interval); + + +/* +* +* Step 2 -> This function will truncate some bits to produce +* a binary sequence of 32-bit. The 160-bit binary +* sequence should in big-endian binary format for +* processing. +*/ + +uint32_t DT(uint8_t* rawData); +#endif \ No newline at end of file diff --git a/include/rfc6238.h b/include/rfc6238.h new file mode 100644 index 0000000..353a1de --- /dev/null +++ b/include/rfc6238.h @@ -0,0 +1,16 @@ +#ifndef RFC6238_H +#define RFC6238_H + +#include +#include +#include +#include + + +#include "rfc4226.h" + +#define TS 30 /* time step in seconds, default value */ + +uint32_t TOTP(uint8_t* key, size_t kl, uint64_t time, int digits); +time_t get_time(time_t T0); +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f580203 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(${OTP_LIBRARY} STATIC + "rfc4226.c" + "rfc6238.c") + +target_include_directories(${OTP_LIBRARY} PUBLIC + ${HEADER_DIR} + ${OPENSSL_HEADER}) + +target_link_libraries(${OTP_LIBRARY} PUBLIC + ${OPENSSL_LIB} + m) \ No newline at end of file diff --git a/src/rfc4226.c b/src/rfc4226.c new file mode 100644 index 0000000..15b2a15 --- /dev/null +++ b/src/rfc4226.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include +#include +#include "rfc4226.h" + +uint8_t* +hmac(unsigned char* key, int kl, uint64_t interval) { + + return (uint8_t*)HMAC(EVP_sha1(), key, kl, + (const unsigned char*)&interval, sizeof(interval), NULL, 0); + +} + +uint32_t +DT(uint8_t* rawData) { + + uint64_t offset; + uint32_t bin_code; + + + /* + * Truncate to 32-bit binary sequence. + */ + offset = rawData[19] & 0xf; + + bin_code = (rawData[offset] & 0x7f) << 24 + | (rawData[offset + 1] & 0xff) << 16 + | (rawData[offset + 2] & 0xff) << 8 + | (rawData[offset + 3] & 0xff); + + + return bin_code; +} + +uint32_t +mod_hotp(uint32_t bin_code, int digits) { + + int power = pow(10, digits); + + uint32_t otp = bin_code % power; + + return otp; + +} + +int +machine_endianness_type() { + + unsigned short int a = 1; + char ist_byte = *((char*)&a); + if (ist_byte == 0) + return 0; + else if (ist_byte == 1) + return 1; +} + +uint32_t +HOTP(uint8_t* key, size_t kl, uint64_t interval, int digits) { + + uint8_t* rawData; + uint32_t result; + uint32_t endianness; + + /* + * Converting the interval from little endian + * to big endian, if required + */ + if (machine_endianness_type()) { + + interval = ((interval & 0x00000000ffffffff) << 32) | ((interval & 0xffffffff00000000) >> 32); + interval = ((interval & 0x0000ffff0000ffff) << 16) | ((interval & 0xffff0000ffff0000) >> 16); + interval = ((interval & 0x00ff00ff00ff00ff) << 8) | ((interval & 0xff00ff00ff00ff00) >> 8); + + } + + //Step - 1, get the hashed integer value. + rawData = (uint8_t*)hmac(key, kl, interval); + + //Step - 2, get dynamically truncated code. + uint32_t truncData = DT(rawData); + + //Step - 3 calculate the final result by modding it. + result = mod_hotp(truncData, digits); + + return result; +} + diff --git a/src/rfc6238.c b/src/rfc6238.c new file mode 100644 index 0000000..43b3da7 --- /dev/null +++ b/src/rfc6238.c @@ -0,0 +1,17 @@ +#include "rfc6238.h" + + +time_t +get_time(time_t t0) +{ + return floor((time(NULL) - t0) / TS); +} + +uint32_t +TOTP(uint8_t* key, size_t kl, uint64_t time, int digits) +{ + uint32_t totp; + + totp = HOTP(key, kl, time, digits); + return totp; +} \ No newline at end of file