mirror of
https://gitlab.com/octospacc/SmolOTP.git
synced 2025-03-16 08:50:07 +01:00
v1.0
This commit is contained in:
parent
e452947440
commit
58445055f5
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
*.nds
|
||||
*.elf
|
122
Makefile
Normal file
122
Makefile
Normal file
@ -0,0 +1,122 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITARM)),)
|
||||
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
|
||||
endif
|
||||
|
||||
include $(DEVKITARM)/ds_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# INCLUDES is a list of directories containing extra header files
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := SmolOTP
|
||||
BUILD := build
|
||||
SOURCES := gfx source data
|
||||
INCLUDES := include build
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -mthumb -mthumb-interwork
|
||||
|
||||
CFLAGS := -g -Wall -O2\
|
||||
-march=armv5te -mtune=arm946e-s -fomit-frame-pointer\
|
||||
-ffast-math \
|
||||
$(ARCH)
|
||||
|
||||
CFLAGS += $(INCLUDE) -DARM9 # -DDEBUG
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# any extra libraries we wish to link with the project
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBS := -lfat -lnds9 -lm
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(LIBNDS)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir))
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
BINFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.bin)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES := $(BINFILES:.bin=.o) \
|
||||
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
.PHONY: $(BUILD) clean
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
$(BUILD):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(TARGET).ds.gba
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
$(OUTPUT).nds : $(OUTPUT).elf
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
%.o : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# SmolOTP
|
||||
|
||||
Barebones OTP application designed for immediate usage on the simplest on devices.
|
||||
|
||||
Download binary releases: <https://gitlab.com/octospacc/SmolOTP/-/releases>
|
||||
|
||||
## Current Support
|
||||
|
||||
Platforms:
|
||||
* Nintendo DS
|
||||
|
||||
OTP protocols:
|
||||
* TOTP
|
||||
|
||||
## Planned Features
|
||||
|
||||
* More platforms (via LibMultiSpacc)
|
||||
* Encryption of secrets, unlock via fast-usable key combos
|
||||
* Changing options (time configuration, adding secrets, ...) in-app
|
||||
* Better gathering of time/date with less configuration needed by users
|
||||
* Handling unlimited secrets (currently there is a 256 hard limit + much lower soft limit because of no screen scrolling lol)
|
||||
|
||||
## Credits
|
||||
|
||||
This app integrates the following third-party libraries:
|
||||
|
||||
* TOTP protocol implementation: <https://github.com/fmount/c_otp>
|
||||
* HMAC+SHA1 protocol implementation: <https://github.com/kokke/tiny-HMAC-c>
|
||||
* INI configuration parser: <https://github.com/benhoyt/inih>
|
33
source/hmac.c
Normal file
33
source/hmac.c
Normal file
@ -0,0 +1,33 @@
|
||||
#include "hmac.h"
|
||||
|
||||
/* function doing the HMAC-SHA-1 calculation */
|
||||
void hmac_sha1(const uint8_t* key, const uint32_t keysize, const uint8_t* msg, const uint32_t msgsize, uint8_t* output)
|
||||
{
|
||||
struct sha1 outer, inner;
|
||||
uint8_t tmp;
|
||||
|
||||
sha1_reset(&outer);
|
||||
sha1_reset(&inner);
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < keysize; ++i)
|
||||
{
|
||||
tmp = key[i] ^ 0x5C;
|
||||
sha1_input(&outer, &tmp, 1);
|
||||
tmp = key[i] ^ 0x36;
|
||||
sha1_input(&inner, &tmp, 1);
|
||||
}
|
||||
for (; i < 64; ++i)
|
||||
{
|
||||
tmp = 0x5C;
|
||||
sha1_input(&outer, &tmp, 1);
|
||||
tmp = 0x36;
|
||||
sha1_input(&inner, &tmp, 1);
|
||||
}
|
||||
|
||||
sha1_input(&inner, msg, msgsize);
|
||||
sha1_result(&inner, output);
|
||||
|
||||
sha1_input(&outer, output, HMAC_SHA1_HASH_SIZE);
|
||||
sha1_result(&outer, output);
|
||||
}
|
19
source/hmac.h
Normal file
19
source/hmac.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef __HMAC_H__
|
||||
#define __HMAC_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "sha1.h"
|
||||
|
||||
#define HMAC_SHA1_HASH_SIZE 20
|
||||
|
||||
/***********************************************************************'
|
||||
* HMAC(K,m) : HMAC SHA1
|
||||
* @param key : secret key
|
||||
* @param keysize : key-length ín bytes
|
||||
* @param msg : msg to calculate HMAC over
|
||||
* @param msgsize : msg-length in bytes
|
||||
* @param output : writeable buffer with at least 20 bytes available
|
||||
*/
|
||||
void hmac_sha1(const uint8_t* key, const uint32_t keysize, const uint8_t* msg, const uint32_t msgsize, uint8_t* output);
|
||||
|
||||
#endif /* __HMAC_H__ */
|
304
source/ini.c
Normal file
304
source/ini.c
Normal file
@ -0,0 +1,304 @@
|
||||
/* inih -- simple .INI file parser
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
Copyright (C) 2009-2020, Ben Hoyt
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
#if !INI_USE_STACK
|
||||
#if INI_CUSTOM_ALLOCATOR
|
||||
#include <stddef.h>
|
||||
void* ini_malloc(size_t size);
|
||||
void ini_free(void* ptr);
|
||||
void* ini_realloc(void* ptr, size_t size);
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#define ini_malloc malloc
|
||||
#define ini_free free
|
||||
#define ini_realloc realloc
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_SECTION 50
|
||||
#define MAX_NAME 50
|
||||
|
||||
/* Used by ini_parse_string() to keep track of string parsing state. */
|
||||
typedef struct {
|
||||
const char* ptr;
|
||||
size_t num_left;
|
||||
} ini_parse_string_ctx;
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
static char* rstrip(char* s)
|
||||
{
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char)(*--p)))
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
static char* lskip(const char* s)
|
||||
{
|
||||
while (*s && isspace((unsigned char)(*s)))
|
||||
s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to NUL at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
static char* find_chars_or_comment(const char* s, const char* chars)
|
||||
{
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
int was_space = 0;
|
||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char)(*s));
|
||||
s++;
|
||||
}
|
||||
#else
|
||||
while (*s && (!chars || !strchr(chars, *s))) {
|
||||
s++;
|
||||
}
|
||||
#endif
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Similar to strncpy, but ensures dest (size bytes) is
|
||||
NUL-terminated, and doesn't pad with NULs. */
|
||||
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||
{
|
||||
/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
|
||||
size_t i;
|
||||
for (i = 0; i < size - 1 && src[i]; i++)
|
||||
dest[i] = src[i];
|
||||
dest[i] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user)
|
||||
{
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
#if INI_USE_STACK
|
||||
char line[INI_MAX_LINE];
|
||||
size_t max_line = INI_MAX_LINE;
|
||||
#else
|
||||
char* line;
|
||||
size_t max_line = INI_INITIAL_ALLOC;
|
||||
#endif
|
||||
#if INI_ALLOW_REALLOC && !INI_USE_STACK
|
||||
char* new_line;
|
||||
size_t offset;
|
||||
#endif
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
#if !INI_USE_STACK
|
||||
line = (char*)ini_malloc(INI_INITIAL_ALLOC);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if INI_HANDLER_LINENO
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||
#else
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, (int)max_line, stream) != NULL) {
|
||||
#if INI_ALLOW_REALLOC && !INI_USE_STACK
|
||||
offset = strlen(line);
|
||||
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||
max_line *= 2;
|
||||
if (max_line > INI_MAX_LINE)
|
||||
max_line = INI_MAX_LINE;
|
||||
new_line = ini_realloc(line, max_line);
|
||||
if (!new_line) {
|
||||
ini_free(line);
|
||||
return -2;
|
||||
}
|
||||
line = new_line;
|
||||
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
|
||||
break;
|
||||
if (max_line >= INI_MAX_LINE)
|
||||
break;
|
||||
offset += strlen(line + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
#if INI_ALLOW_BOM
|
||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||
(unsigned char)start[1] == 0xBB &&
|
||||
(unsigned char)start[2] == 0xBF) {
|
||||
start += 3;
|
||||
}
|
||||
#endif
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
||||
/* Start-of-line comment */
|
||||
}
|
||||
#if INI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && start > line) {
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(start, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
rstrip(start);
|
||||
#endif
|
||||
/* Non-blank line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python configparser). */
|
||||
if (!HANDLER(user, section, prev_name, start) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
#if INI_CALL_HANDLER_ON_NEW_SECTION
|
||||
if (!HANDLER(user, section, NULL, NULL) && !error)
|
||||
error = lineno;
|
||||
#endif
|
||||
}
|
||||
else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = end + 1;
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
#endif
|
||||
value = lskip(value);
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!HANDLER(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
#if INI_ALLOW_NO_VALUE
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
if (!HANDLER(user, section, name, NULL) && !error)
|
||||
error = lineno;
|
||||
#else
|
||||
error = lineno;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if INI_STOP_ON_FIRST_ERROR
|
||||
if (error)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !INI_USE_STACK
|
||||
ini_free(line);
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||
{
|
||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||
{
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file)
|
||||
return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* An ini_reader function to read the next line from a string buffer. This
|
||||
is the fgets() equivalent used by ini_parse_string(). */
|
||||
static char* ini_reader_string(char* str, int num, void* stream) {
|
||||
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
|
||||
const char* ctx_ptr = ctx->ptr;
|
||||
size_t ctx_num_left = ctx->num_left;
|
||||
char* strp = str;
|
||||
char c;
|
||||
|
||||
if (ctx_num_left == 0 || num < 2)
|
||||
return NULL;
|
||||
|
||||
while (num > 1 && ctx_num_left != 0) {
|
||||
c = *ctx_ptr++;
|
||||
ctx_num_left--;
|
||||
*strp++ = c;
|
||||
if (c == '\n')
|
||||
break;
|
||||
num--;
|
||||
}
|
||||
|
||||
*strp = '\0';
|
||||
ctx->ptr = ctx_ptr;
|
||||
ctx->num_left = ctx_num_left;
|
||||
return str;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_string(const char* string, ini_handler handler, void* user) {
|
||||
ini_parse_string_ctx ctx;
|
||||
|
||||
ctx.ptr = string;
|
||||
ctx.num_left = strlen(string);
|
||||
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
|
||||
user);
|
||||
}
|
178
source/ini.h
Normal file
178
source/ini.h
Normal file
@ -0,0 +1,178 @@
|
||||
/* inih -- simple .INI file parser
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
Copyright (C) 2009-2020, Ben Hoyt
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef INI_H
|
||||
#define INI_H
|
||||
|
||||
/* Make this header file easier to include in C++ code */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
||||
#ifndef INI_HANDLER_LINENO
|
||||
#define INI_HANDLER_LINENO 0
|
||||
#endif
|
||||
|
||||
/* Visibility symbols, required for Windows DLLs */
|
||||
#ifndef INI_API
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
# ifdef INI_SHARED_LIB
|
||||
# ifdef INI_SHARED_LIB_BUILDING
|
||||
# define INI_API __declspec(dllexport)
|
||||
# else
|
||||
# define INI_API __declspec(dllimport)
|
||||
# endif
|
||||
# else
|
||||
# define INI_API
|
||||
# endif
|
||||
#else
|
||||
# if defined(__GNUC__) && __GNUC__ >= 4
|
||||
# define INI_API __attribute__ ((visibility ("default")))
|
||||
# else
|
||||
# define INI_API
|
||||
# endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
#if INI_HANDLER_LINENO
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value,
|
||||
int lineno);
|
||||
#else
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value);
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's configparser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||
error (only when INI_USE_STACK is zero).
|
||||
*/
|
||||
INI_API int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||
filename. Used for implementing custom or string-based I/O (see also
|
||||
ini_parse_string). */
|
||||
INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
||||
instead of a file. Useful for parsing INI data from a network socket or
|
||||
already in memory. */
|
||||
INI_API int ini_parse_string(const char* string, ini_handler handler, void* user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
configparser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef INI_ALLOW_MULTILINE
|
||||
#define INI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||
the file. See https://github.com/benhoyt/inih/issues/21 */
|
||||
#ifndef INI_ALLOW_BOM
|
||||
#define INI_ALLOW_BOM 1
|
||||
#endif
|
||||
|
||||
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
||||
both ; and # comments at the start of a line by default. */
|
||||
#ifndef INI_START_COMMENT_PREFIXES
|
||||
#define INI_START_COMMENT_PREFIXES ";#"
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||
Python 3.2+ configparser behaviour. */
|
||||
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||
#endif
|
||||
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||
#endif
|
||||
|
||||
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
||||
#ifndef INI_USE_STACK
|
||||
#define INI_USE_STACK 1
|
||||
#endif
|
||||
|
||||
/* Maximum line length for any line in INI file (stack or heap). Note that
|
||||
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
||||
#ifndef INI_MAX_LINE
|
||||
#define INI_MAX_LINE 200
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
|
||||
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
|
||||
zero. */
|
||||
#ifndef INI_ALLOW_REALLOC
|
||||
#define INI_ALLOW_REALLOC 0
|
||||
#endif
|
||||
|
||||
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
|
||||
is zero. */
|
||||
#ifndef INI_INITIAL_ALLOC
|
||||
#define INI_INITIAL_ALLOC 200
|
||||
#endif
|
||||
|
||||
/* Stop parsing on first error (default is to keep parsing). */
|
||||
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||
#define INI_STOP_ON_FIRST_ERROR 0
|
||||
#endif
|
||||
|
||||
/* Nonzero to call the handler at the start of each new section (with
|
||||
name and value NULL). Default is to only call the handler on
|
||||
each name=value pair. */
|
||||
#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
|
||||
#define INI_CALL_HANDLER_ON_NEW_SECTION 0
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
|
||||
call the handler with value NULL in this case. Default is to treat
|
||||
no-value lines as an error. */
|
||||
#ifndef INI_ALLOW_NO_VALUE
|
||||
#define INI_ALLOW_NO_VALUE 0
|
||||
#endif
|
||||
|
||||
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
|
||||
allocation functions (INI_USE_STACK must also be 0). These functions must
|
||||
have the same signatures as malloc/free/realloc and behave in a similar
|
||||
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
|
||||
#ifndef INI_CUSTOM_ALLOCATOR
|
||||
#define INI_CUSTOM_ALLOCATOR 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* INI_H */
|
239
source/main.c
Normal file
239
source/main.c
Normal file
@ -0,0 +1,239 @@
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <nds.h>
|
||||
#include <fat.h>
|
||||
|
||||
#include "rfc4226.h"
|
||||
#include "rfc6238.h"
|
||||
#include "utils.h"
|
||||
#include "parser.h"
|
||||
#include "ini.h"
|
||||
|
||||
#define T0 0
|
||||
#define DIGITS 6
|
||||
#define VALIDITY 30
|
||||
#define TIME 2
|
||||
#define VERSION 1.0
|
||||
|
||||
#define AppConfigFile "/.AppData/SmolOTP/Config.ini"
|
||||
#define MaxSecretLength 32
|
||||
#define MaxSecretsCount 256
|
||||
|
||||
extern NODE *provider_list = NULL;
|
||||
|
||||
typedef struct AppConfigType {
|
||||
int timezone;
|
||||
bool useDaylightSavings;
|
||||
int totpSecretsCount;
|
||||
char totpNames[MaxSecretsCount][MaxSecretLength];
|
||||
char totpSecrets[MaxSecretsCount][MaxSecretLength];
|
||||
uint32_t totpResults[MaxSecretsCount];
|
||||
} AppConfigType;
|
||||
|
||||
char currentTotpSecret[MaxSecretLength];
|
||||
|
||||
uint32_t getTotp(char secret[], int timezone)
|
||||
{
|
||||
size_t pos;
|
||||
size_t len = strlen(secret);
|
||||
size_t keylen;
|
||||
time_t curtime;
|
||||
uint8_t *keysec;
|
||||
uint32_t result;
|
||||
|
||||
if (validate_b32key(secret, len, pos) == 1) {
|
||||
// invalid secret
|
||||
return UINT32_MAX;
|
||||
} else {
|
||||
keysec = (uint8_t *)secret;
|
||||
keylen = decode_b32key(&keysec, len);
|
||||
curtime = getTotpTime(T0, VALIDITY, timezone);
|
||||
result = TOTP(keysec, keylen, curtime, DIGITS);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
void waitUserExit(int code)
|
||||
{
|
||||
int gamepadKeys;
|
||||
while(1) {
|
||||
swiWaitForVBlank();
|
||||
scanKeys();
|
||||
gamepadKeys = keysDown();
|
||||
if (gamepadKeys & KEY_START || gamepadKeys & KEY_SELECT) {
|
||||
exit(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calcOtps(AppConfigType *AppConfig)
|
||||
{
|
||||
for (int i=0; i<AppConfig->totpSecretsCount; i++)
|
||||
{
|
||||
// we copy the secret in a temporary variable because the calculation corrupts it (?)
|
||||
strcpy(currentTotpSecret, AppConfig->totpSecrets[i]);
|
||||
AppConfig->totpResults[i] = getTotp(currentTotpSecret, AppConfig->timezone);
|
||||
}
|
||||
}
|
||||
|
||||
void printOtps(AppConfigType *AppConfig)
|
||||
{
|
||||
if (AppConfig->totpSecretsCount == 0)
|
||||
{
|
||||
iprintf("\x1b[5;0H No Secrets Defined in \n \"%s\"\n", AppConfigFile);
|
||||
}
|
||||
for (int i=0; i<AppConfig->totpSecretsCount; i++)
|
||||
{
|
||||
iprintf("\x1b[%d;0H %s\n", 5+(i*2), AppConfig->totpNames[i]);
|
||||
|
||||
if (AppConfig->totpResults[i] == UINT32_MAX) {
|
||||
iprintf("\x1b[%d;23H INVALID\n", 5+(i*2));
|
||||
} else {
|
||||
iprintf("\x1b[%d;24H %06u\n", 5+(i*2), AppConfig->totpResults[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int HandleAppConfig(AppConfigType *AppConfig, const char* section, const char* name, const char* value)
|
||||
{
|
||||
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
|
||||
if (MATCH("Options", "TimezoneOffset"))
|
||||
{
|
||||
AppConfig->timezone = atoi(value);
|
||||
}
|
||||
//else if (MATCH("Options", "UseDaylightSavingTime"))
|
||||
//{
|
||||
// AppConfig->useDaylightSavings = (strcmp(strlwr(value), "true") == 0 ? true : false);
|
||||
//}
|
||||
else if (strcmp(section, "Secrets") == 0)
|
||||
{
|
||||
strcpy(AppConfig->totpNames[AppConfig->totpSecretsCount], name);
|
||||
strcpy(AppConfig->totpSecrets[AppConfig->totpSecretsCount], value);
|
||||
AppConfig->totpSecretsCount++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void RewriteAppConfig(AppConfigType *AppConfig)
|
||||
{
|
||||
FILE *file = fopen(AppConfigFile, "w");
|
||||
fprintf(file,
|
||||
"\n[Options]\n"
|
||||
"# Note: For now Daylight Saving Time is not implemented, so set your UTC offset to account for that"
|
||||
"TimezoneOffset = %d\n"
|
||||
"\n[Secrets]\n",
|
||||
AppConfig->timezone);
|
||||
for (int i=0; i<AppConfig->totpSecretsCount; i++)
|
||||
{
|
||||
fprintf(file, "%s = %s\n", AppConfig->totpNames[i], AppConfig->totpSecrets[i]);
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// TODO
|
||||
/*bool isinDaylightSavings(int month, int day, int weekday, int hours, int minutes)
|
||||
{
|
||||
// TODO: handle daylight savings start/end dates with alternatives to the european standard
|
||||
// https://en.m.wikipedia.org/wiki/Daylight_saving_time_by_country
|
||||
//bool isSavingsMonth = (month >= 3 && month <= 10);
|
||||
//return (isSavingsMonth && isSwitchDay weekday == && );
|
||||
return false;
|
||||
}*/
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int totpViewStart = 0;
|
||||
int totpViewEnd = 5;
|
||||
int gamepadKeys;
|
||||
time_t unixTime;
|
||||
struct tm *timeStruct;
|
||||
int year, month, day, weekday, hours, minutes, seconds;
|
||||
bool inDaylightSavings;
|
||||
char totpNames[MaxSecretsCount][MaxSecretLength];
|
||||
char totpSecrets[MaxSecretsCount][MaxSecretLength];
|
||||
uint32_t totpResults[MaxSecretsCount];
|
||||
|
||||
AppConfigType AppConfig = {
|
||||
.timezone = 0,
|
||||
.useDaylightSavings = false,
|
||||
.totpSecretsCount = 0,
|
||||
.totpNames = totpNames,
|
||||
.totpSecrets = totpSecrets,
|
||||
.totpResults = totpResults,
|
||||
};
|
||||
|
||||
consoleDemoInit();
|
||||
|
||||
if (!fatInitDefault())
|
||||
{
|
||||
iprintf("fatInitDefault failure\nPress START to exit...\n");
|
||||
waitUserExit(-1);
|
||||
}
|
||||
|
||||
mkdir("/.AppData");
|
||||
mkdir("/.AppData/SmolOTP");
|
||||
|
||||
if (ini_parse(AppConfigFile, HandleAppConfig, &AppConfig) < 0)
|
||||
{
|
||||
RewriteAppConfig(&AppConfig);
|
||||
}
|
||||
|
||||
/*if (AppConfig.useDaylightSavings && isinDaylightSavings(month, day, weekday, hours, minutes))
|
||||
{
|
||||
inDaylightSavings = true;
|
||||
AppConfig.timezone++;
|
||||
}*/
|
||||
|
||||
calcOtps(&AppConfig);
|
||||
printOtps(&AppConfig);
|
||||
|
||||
while(1)
|
||||
{
|
||||
unixTime = getUnixTime(AppConfig.timezone);
|
||||
timeStruct = gmtime((const time_t *)&unixTime);
|
||||
|
||||
year = timeStruct->tm_year + 1900;
|
||||
month = timeStruct->tm_mon;
|
||||
day = timeStruct->tm_mday;
|
||||
//weekday = timeStruct->tm_wday;
|
||||
hours = timeStruct->tm_hour;
|
||||
minutes = timeStruct->tm_min;
|
||||
seconds = timeStruct->tm_sec;
|
||||
|
||||
iprintf("\x1b[0;0H UTC Time: %04d-%02d-%02d %02d:%02d:%02d\n (Timezone Offset: %s%d)\n\n--------------------------------",
|
||||
year, month, day, hours, minutes, seconds,
|
||||
(AppConfig.timezone >= 0 ? "+" : ""), AppConfig.timezone//,
|
||||
//(AppConfig.useDaylightSavings ? "" : "DST Adjustments are Disabled.")
|
||||
);
|
||||
|
||||
if (seconds % 30 == 0)
|
||||
{
|
||||
calcOtps(&AppConfig);
|
||||
printOtps(&AppConfig);
|
||||
}
|
||||
|
||||
swiWaitForVBlank();
|
||||
scanKeys();
|
||||
gamepadKeys = keysDown();
|
||||
|
||||
// TODO: add secrets encryption, and decryption via keypad combos
|
||||
if (gamepadKeys & KEY_START || gamepadKeys & KEY_SELECT)
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
else if (totpViewStart > 0 && gamepadKeys & KEY_UP)
|
||||
{
|
||||
// scroll list up ...
|
||||
}
|
||||
else if (totpViewEnd < AppConfig.totpSecretsCount && gamepadKeys & KEY_DOWN)
|
||||
{
|
||||
// scroll list down ...
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
123
source/parser.c
Normal file
123
source/parser.c
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "plist.h"
|
||||
#include "parser.h"
|
||||
|
||||
PROVIDER split_str(char *spl, char delim)
|
||||
{
|
||||
char *tmp_name;
|
||||
char *tmp_secret;
|
||||
PROVIDER p;
|
||||
size_t count = 0;
|
||||
|
||||
size_t totlen = strlen(spl) - 2;
|
||||
|
||||
//Get break point
|
||||
do {
|
||||
count++;
|
||||
} while (spl[count] != delim);
|
||||
|
||||
tmp_name = (char *) malloc(count * sizeof(char) + 1);
|
||||
tmp_secret = (char *) malloc((totlen-count) * sizeof(char) + 1);
|
||||
|
||||
/*
|
||||
* Get first part of the string
|
||||
*/
|
||||
memcpy(tmp_name, spl, count);
|
||||
tmp_name[count] = '\0';
|
||||
/*
|
||||
* Get second part of the string
|
||||
*/
|
||||
memcpy(tmp_secret, spl+(count+1), (totlen-count));
|
||||
tmp_secret[(totlen-count)] = '\0';
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[GOT LEN]: %ld\n", strlen(spl));
|
||||
printf("[PROVIDER SECTION]: %ld characters\n", count);
|
||||
printf("[GOT NAME]: %s\n", tmp_name);
|
||||
printf("[SECRET SECTION]: %ld\n", (strlen(spl)-count+1));
|
||||
printf("[GOT SECRET]: %s\n", tmp_secret);
|
||||
#endif
|
||||
|
||||
p = (PROVIDER) {
|
||||
.pname = tmp_name,
|
||||
.psecret = tmp_secret,
|
||||
.otpvalue = NULL
|
||||
};
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void process_provider(NODE **plist, char *line)
|
||||
{
|
||||
PROVIDER p;
|
||||
p = split_str(line, ':');
|
||||
push(plist, p.pname, p.psecret, p.otpvalue);
|
||||
}
|
||||
|
||||
// reimplementation of getline() to stop the compiler from crying (<https://stackoverflow.com/a/76142157>)
|
||||
ssize_t getline(char **restrict buffer, size_t *restrict size, FILE *restrict fp) {
|
||||
register int c;
|
||||
register char *cs = NULL;
|
||||
|
||||
if (cs == NULL) {
|
||||
register int length = 0;
|
||||
while ((c = getc(fp)) != EOF) {
|
||||
cs = (char *)realloc(cs, ++length+1);
|
||||
if ((*(cs + length - 1) = c) == '\n') {
|
||||
*(cs + length) = '\0';
|
||||
*buffer = cs;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (ssize_t)(*size = length);
|
||||
} else {
|
||||
while (--(*size) > 0 && (c = getc(fp)) != EOF) {
|
||||
if ((*cs++ = c) == '\n')
|
||||
break;
|
||||
}
|
||||
*cs = '\0';
|
||||
}
|
||||
return (ssize_t)(*size=strlen(*buffer));
|
||||
}
|
||||
|
||||
void load_providers(char *fname)
|
||||
{
|
||||
FILE *f;
|
||||
size_t len = 1024;
|
||||
|
||||
if (fname == NULL)
|
||||
exit(ENOENT);
|
||||
f = fopen(fname, "r");
|
||||
if (f == NULL)
|
||||
exit(ENOENT);
|
||||
char *line = NULL;
|
||||
|
||||
while (getline(&line, &len, f) != -1) {
|
||||
if (line[0] != '#')
|
||||
process_provider(&provider_list, line);
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(f);
|
||||
}
|
27
source/parser.h
Normal file
27
source/parser.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "plist.h"
|
||||
|
||||
extern NODE *provider_list;
|
||||
|
||||
PROVIDER split_str(char *spl, char delim);
|
||||
void process_provider(NODE **plist, char *line);
|
||||
void load_providers(char *fname);
|
142
source/plist.c
Normal file
142
source/plist.c
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "plist.h"
|
||||
|
||||
size_t get_len(NODE *head)
|
||||
{
|
||||
NODE *cur = NULL;
|
||||
cur = head;
|
||||
size_t length = 0;
|
||||
|
||||
while (cur != NULL) {
|
||||
cur = cur->next;
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
bool exists(NODE *head, NODE *target)
|
||||
{
|
||||
printf("Check if the target node exists in list\n");
|
||||
|
||||
NODE *cur = NULL;
|
||||
cur = head;
|
||||
while (cur != NULL) {
|
||||
if ((cur->p)->pname == (target->p)->pname)
|
||||
return 1;
|
||||
cur = cur->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
NODE *get_node(NODE *head, char *pname)
|
||||
{
|
||||
NODE *cur = NULL;
|
||||
cur = head;
|
||||
while (cur != NULL) {
|
||||
if ((cur->p)->pname == pname) {
|
||||
return cur;
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int update_value(NODE **head, char *pname, uint32_t optvalue)
|
||||
{
|
||||
NODE *cur;
|
||||
cur = *head;
|
||||
uint32_t *x = &optvalue;
|
||||
while (cur != NULL) {
|
||||
if ((cur->p)->pname == pname) {
|
||||
(cur->p)->otpvalue = *x;
|
||||
return 0;
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void push(NODE **head, char *pname, char *psecret, uint32_t *otpvalue)
|
||||
{
|
||||
NODE *cur = (NODE *) malloc(sizeof(NODE));
|
||||
PROVIDER *p = (PROVIDER *) malloc(sizeof(PROVIDER));
|
||||
|
||||
p->pname = pname;
|
||||
p->psecret = psecret;
|
||||
p->otpvalue = otpvalue;
|
||||
|
||||
cur->p = p;
|
||||
|
||||
cur->next = *head;
|
||||
*head = cur;
|
||||
}
|
||||
|
||||
NODE *pop(NODE **head)
|
||||
{
|
||||
NODE *tmp = *head;
|
||||
*head = (*head)->next;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void del(char *del, NODE *head)
|
||||
{
|
||||
if(head == NULL)
|
||||
fprintf(stderr, "No valid list, no head found\n");
|
||||
|
||||
NODE *cur = NULL;
|
||||
NODE *prev = NULL;
|
||||
|
||||
cur = prev = head;
|
||||
|
||||
while(cur != NULL && (strcmp((cur->p)->pname, del) != 0)) {
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
// Reached the end, should return ..
|
||||
if(cur == NULL)
|
||||
return;
|
||||
/* Found the pname in the list, free the node and
|
||||
* modify the pointer to next
|
||||
*/
|
||||
prev->next = cur->next;
|
||||
free(cur);
|
||||
}
|
||||
|
||||
void freeProvider(PROVIDER *p)
|
||||
{
|
||||
free(p->pname);
|
||||
free(p->psecret);
|
||||
free((PROVIDER *)p);
|
||||
}
|
||||
|
||||
void freeList(NODE *head)
|
||||
{
|
||||
NODE *tmp;
|
||||
|
||||
while (head != NULL) {
|
||||
tmp = head;
|
||||
#ifdef DEBUG
|
||||
printf("Deleting Provider %s\n", (tmp->p)->pname);
|
||||
#endif
|
||||
freeProvider(tmp->p);
|
||||
head = head->next;
|
||||
free(tmp);
|
||||
}
|
||||
}
|
44
source/plist.h
Normal file
44
source/plist.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PLIST_H
|
||||
#define PLIST_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
char *pname;
|
||||
char *psecret;
|
||||
uint32_t *otpvalue;
|
||||
} PROVIDER;
|
||||
|
||||
typedef struct Node {
|
||||
PROVIDER *p;
|
||||
struct Node *next;
|
||||
} NODE;
|
||||
|
||||
void freeList(NODE *head);
|
||||
void freeProvider(PROVIDER *p);
|
||||
size_t get_len(NODE *head);
|
||||
int update_value(NODE **head, char *pname, uint32_t optvalue);
|
||||
void push(NODE **head, char *pname, char *psecret, uint32_t *otpvalue);
|
||||
void del(char *del, NODE *head);
|
||||
bool exists(NODE *head, NODE *target);
|
||||
NODE *pop(NODE **head);
|
||||
NODE *get_node(NODE *head, char *pname);
|
||||
|
||||
#endif
|
96
source/rfc4226.c
Normal file
96
source/rfc4226.c
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "hmac.h"
|
||||
|
||||
char currentHmacResult[HMAC_SHA1_HASH_SIZE];
|
||||
|
||||
uint8_t *hmac(unsigned char *key, int kl, uint64_t interval)
|
||||
{
|
||||
hmac_sha1(key, kl, &interval, sizeof(interval), ¤tHmacResult);
|
||||
return currentHmacResult;
|
||||
}
|
||||
|
||||
uint32_t DT(uint8_t *digest)
|
||||
{
|
||||
uint64_t offset;
|
||||
uint32_t bin_code;
|
||||
|
||||
#ifdef DEBUG
|
||||
char mdString[40];
|
||||
for (int i = 0; i < 20; i++)
|
||||
sprintf(&mdString[i*2], "%02x", (unsigned int)digest[i]);
|
||||
printf("HMAC digest: %s\n", mdString);
|
||||
#endif
|
||||
|
||||
// dynamically truncates hash
|
||||
offset = digest[19] & 0x0f;
|
||||
|
||||
bin_code = (digest[offset] & 0x7f) << 24
|
||||
| (digest[offset+1] & 0xff) << 16
|
||||
| (digest[offset+2] & 0xff) << 8
|
||||
| (digest[offset+3] & 0xff);
|
||||
|
||||
// truncates code to 6 digits
|
||||
#ifdef DEBUG
|
||||
printf("OFFSET: %d\n", offset);
|
||||
printf("\nDBC1: %d\n", bin_code);
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t HOTP(uint8_t *key, size_t kl, uint64_t interval, int digits)
|
||||
{
|
||||
uint8_t *digest;
|
||||
uint32_t result;
|
||||
uint32_t endianness;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("KEY IS: %s\n", key);
|
||||
printf("KEY LEN IS: %d\n", kl);
|
||||
printf("COUNTER IS: %d\n", interval);
|
||||
#endif
|
||||
|
||||
endianness = 0xdeadbeef;
|
||||
if ((*(const uint8_t *)&endianness) == 0xef) {
|
||||
interval = ((interval & 0x00000000ffffffff) << 32) | ((interval & 0xffffffff00000000) >> 32);
|
||||
interval = ((interval & 0x0000ffff0000ffff) << 16) | ((interval & 0xffff0000ffff0000) >> 16);
|
||||
interval = ((interval & 0x00ff00ff00ff00ff) << 8) | ((interval & 0xff00ff00ff00ff00) >> 8);
|
||||
};
|
||||
|
||||
//First Phase, get the digest of the message using the provided key ...
|
||||
digest = (uint8_t *)hmac(key, kl, interval);
|
||||
|
||||
//Second Phase, get the dbc from the algorithm
|
||||
uint32_t dbc = DT(digest);
|
||||
|
||||
//Third Phase: calculate the mod_k of the dbc to get the correct number
|
||||
result = mod_hotp(dbc, digits);
|
||||
|
||||
return result;
|
||||
}
|
30
source/rfc4226.h
Normal file
30
source/rfc4226.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RFC4226_H
|
||||
#define RFC4226_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
//MAIN HOTP function
|
||||
uint32_t HOTP(uint8_t *key, size_t kl, uint64_t interval, int digits);
|
||||
|
||||
//First step
|
||||
uint8_t *hmac(unsigned char *key, int kl, uint64_t interval);
|
||||
|
||||
//Second step
|
||||
uint32_t DT(uint8_t *digest);
|
||||
|
||||
#endif
|
34
source/rfc6238.c
Normal file
34
source/rfc6238.c
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#include "rfc6238.h"
|
||||
|
||||
time_t getUnixTime(int timezoneOffset)
|
||||
{
|
||||
// note: subtract, not add, the TZ offset from the system time to get the UTC time based on TZ
|
||||
// eg. if localTime = 12:00, then utcTime = localTime - (+02) = 10:00
|
||||
return (time(NULL) - (timezoneOffset * 60 * 60));
|
||||
}
|
||||
|
||||
time_t getTotpTime(time_t t0, int timestep, int timezoneOffset)
|
||||
{
|
||||
return (floor((getUnixTime(timezoneOffset) - t0) / timestep));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
40
source/rfc6238.h
Normal file
40
source/rfc6238.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RFC6238_H
|
||||
#define RFC6238_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "rfc4226.h"
|
||||
|
||||
#define TS 30 // time step in seconds, default value
|
||||
|
||||
/******** RFC6238 **********
|
||||
*
|
||||
* TOTP = HOTP(k,T) where
|
||||
* K = the supersecret key
|
||||
* T = ( Current Unix time - T0) / X
|
||||
* where X is the Time Step
|
||||
*
|
||||
* *************************/
|
||||
|
||||
uint32_t TOTP(uint8_t *key, size_t kl, uint64_t time, int digits);
|
||||
time_t getUnixTime(int timezoneOffset);
|
||||
time_t getTotpTime(time_t t0, int timestep, int timezoneOffset);
|
||||
|
||||
#endif
|
512
source/sha1.c
Normal file
512
source/sha1.c
Normal file
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* sha1.c
|
||||
*
|
||||
* Description:
|
||||
* This file implements the Secure Hashing Algorithm 1 as
|
||||
* defined in FIPS PUB 180-1 published April 17, 1995.
|
||||
*
|
||||
* The SHA-1, produces a 160-bit message digest for a given
|
||||
* data stream. It should take about 2**n steps to find a
|
||||
* message with the same digest as a given message and
|
||||
* 2**(n/2) to find any two messages with the same digest,
|
||||
* when n is the digest size in bits. Therefore, this
|
||||
* algorithm can serve as a means of providing a
|
||||
* "fingerprint" for a message.
|
||||
*
|
||||
* Caveats:
|
||||
* SHA-1 is designed to work with messages less than 2^64 bits
|
||||
* long. Although SHA-1 allows a message digest to be generated
|
||||
* for messages of any number of bits less than 2^64, this
|
||||
* implementation only works with messages with a length that is
|
||||
* a multiple of the size of an 8-bit character.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sha1.h"
|
||||
|
||||
/* Local Function Prototyptes */
|
||||
static void _pad_block(struct sha1*);
|
||||
static void _process_block(struct sha1*);
|
||||
|
||||
/* SHA1 circular left shift */
|
||||
static uint32_t _circular_shift(const uint32_t nbits, const uint32_t word)
|
||||
{
|
||||
return ((word << nbits) | (word >> (32 - nbits)));
|
||||
}
|
||||
|
||||
/*
|
||||
* sha1_reset
|
||||
*
|
||||
* Description:
|
||||
* This function will initialize the SHA1-context in preparation
|
||||
* for computing a new SHA1 message digest.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The context to reset.
|
||||
*
|
||||
* Returns:
|
||||
* sha Error Code.
|
||||
*
|
||||
*/
|
||||
int sha1_reset(struct sha1* context)
|
||||
{
|
||||
if (context == 0)
|
||||
{
|
||||
return shaNull;
|
||||
}
|
||||
|
||||
context->Length_Low = 0;
|
||||
context->Length_High = 0;
|
||||
context->Message_Block_Index = 0;
|
||||
|
||||
context->Intermediate_Hash[0] = 0x67452301;
|
||||
context->Intermediate_Hash[1] = 0xEFCDAB89;
|
||||
context->Intermediate_Hash[2] = 0x98BADCFE;
|
||||
context->Intermediate_Hash[3] = 0x10325476;
|
||||
context->Intermediate_Hash[4] = 0xC3D2E1F0;
|
||||
|
||||
context->flags = 0;
|
||||
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* sha1_result
|
||||
*
|
||||
* Description:
|
||||
* This function will return the 160-bit message digest into the
|
||||
* Message_Digest array provided by the caller.
|
||||
* NOTE: The first octet of hash is stored in the 0th element,
|
||||
* the last octet of hash in the 19th element.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The context to use to calculate the SHA-1 hash.
|
||||
* Message_Digest: [out]
|
||||
* Where the digest is returned.
|
||||
*
|
||||
* Returns:
|
||||
* sha Error Code.
|
||||
*
|
||||
*/
|
||||
int sha1_result(struct sha1* context, uint8_t Message_Digest[SHA1HashSize])
|
||||
{
|
||||
int i;
|
||||
|
||||
if ( (context == 0)
|
||||
|| (Message_Digest == 0))
|
||||
{
|
||||
return shaNull;
|
||||
}
|
||||
|
||||
if ((context->flags & FLAG_CORRUPTED) != 0)
|
||||
{
|
||||
return shaStateError;
|
||||
}
|
||||
|
||||
if ((context->flags & FLAG_COMPUTED) == 0)
|
||||
{
|
||||
_pad_block(context);
|
||||
|
||||
for (i = 0; i < 64; ++i)
|
||||
{
|
||||
/* message may be sensitive, clear it out */
|
||||
context->Message_Block[i] = 0;
|
||||
}
|
||||
context->Length_Low = 0; /* and clear length */
|
||||
context->Length_High = 0;
|
||||
context->flags |= FLAG_COMPUTED;
|
||||
}
|
||||
|
||||
for (i = 0; i < SHA1HashSize; ++i)
|
||||
{
|
||||
Message_Digest[i] = (context->Intermediate_Hash[i >> 2] >> (8 * (3 - (i & 0x03))));
|
||||
}
|
||||
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* sha1_input
|
||||
*
|
||||
* Description:
|
||||
* This function accepts an array of octets as the next portion
|
||||
* of the message.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The SHA context to update
|
||||
* message_array: [in]
|
||||
* An array of characters representing the next portion of
|
||||
* the message.
|
||||
* length: [in]
|
||||
* The length of the message in message_array
|
||||
*
|
||||
* Returns:
|
||||
* sha Error Code.
|
||||
*
|
||||
*/
|
||||
int sha1_input(struct sha1* context, const uint8_t* message_array, unsigned length)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
if ( (context == 0)
|
||||
|| (message_array == 0))
|
||||
{
|
||||
return shaNull;
|
||||
}
|
||||
|
||||
if ((context->flags & FLAG_COMPUTED) != 0)
|
||||
{
|
||||
context->flags |= FLAG_CORRUPTED;
|
||||
return shaStateError;
|
||||
}
|
||||
|
||||
if ((context->flags & FLAG_CORRUPTED) != 0)
|
||||
{
|
||||
return shaStateError;
|
||||
}
|
||||
|
||||
while ( (length != 0)
|
||||
&& (context->flags == 0))
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index] = (*message_array);
|
||||
|
||||
context->Message_Block_Index += 1;
|
||||
context->Length_Low += 8;
|
||||
|
||||
if (context->Length_Low == 0)
|
||||
{
|
||||
context->Length_High += 1;
|
||||
|
||||
if (context->Length_High == 0)
|
||||
{
|
||||
/* Message is too long */
|
||||
context->flags |= FLAG_CORRUPTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->Message_Block_Index == 64)
|
||||
{
|
||||
_process_block(context);
|
||||
}
|
||||
|
||||
message_array += 1;
|
||||
length -= 1;
|
||||
}
|
||||
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* _process_block
|
||||
*
|
||||
* Description:
|
||||
* This function will process the next 512 bits of the message
|
||||
* stored in the Message_Block array.
|
||||
*
|
||||
* Parameters:
|
||||
* None.
|
||||
*
|
||||
* Returns:
|
||||
* Nothing.
|
||||
*
|
||||
* Comments:
|
||||
|
||||
* Many of the variable names in this code, especially the
|
||||
* single character names, were used because those were the
|
||||
* names used in the publication.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#if 0 // original code
|
||||
static void _process_block(struct sha1 *context)
|
||||
{
|
||||
const uint32_t K[] = /* Constants defined in SHA-1 */
|
||||
{
|
||||
0x5A827999,
|
||||
0x6ED9EBA1,
|
||||
0x8F1BBCDC,
|
||||
0xCA62C1D6
|
||||
};
|
||||
uint32_t t; /* Loop counter */
|
||||
uint32_t temp; /* Temporary word value */
|
||||
uint32_t W[80]; /* Word sequence */
|
||||
uint32_t A, B, C, D, E; /* Word buffers */
|
||||
|
||||
/*
|
||||
* Initialize the first 16 words in the array W
|
||||
*/
|
||||
for (t = 0; t < 16; ++t)
|
||||
{
|
||||
W[t] = context->Message_Block[(t * 4) + 0] << 24;
|
||||
W[t] |= context->Message_Block[(t * 4) + 1] << 16;
|
||||
W[t] |= context->Message_Block[(t * 4) + 2] << 8;
|
||||
W[t] |= context->Message_Block[(t * 4) + 3] << 0;
|
||||
}
|
||||
|
||||
for (t = 16; t < 80; ++t)
|
||||
{
|
||||
W[t] = _circular_shift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]);
|
||||
}
|
||||
|
||||
A = context->Intermediate_Hash[0];
|
||||
B = context->Intermediate_Hash[1];
|
||||
C = context->Intermediate_Hash[2];
|
||||
D = context->Intermediate_Hash[3];
|
||||
E = context->Intermediate_Hash[4];
|
||||
|
||||
for (t = 0; t < 20; ++t)
|
||||
{
|
||||
temp = _circular_shift(5, A) +
|
||||
((B & C) | ((~B) & D)) + E + W[t] + K[0];
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for (; t < 40; ++t)
|
||||
{
|
||||
temp = _circular_shift(5, A) + (B ^ C ^ D) + E + W[t] + K[1];
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for (; t < 60; ++t)
|
||||
{
|
||||
temp = _circular_shift(5, A) +
|
||||
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for (; t < 80; ++t)
|
||||
{
|
||||
temp = _circular_shift(5, A) + (B ^ C ^ D) + E + W[t] + K[3];
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
context->Intermediate_Hash[0] += A;
|
||||
context->Intermediate_Hash[1] += B;
|
||||
context->Intermediate_Hash[2] += C;
|
||||
context->Intermediate_Hash[3] += D;
|
||||
context->Intermediate_Hash[4] += E;
|
||||
|
||||
context->Message_Block_Index = 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
//#define METHOD2
|
||||
void _process_block(struct sha1 *context)
|
||||
{
|
||||
const uint32_t K[] = /* Constants defined in SHA-1 */
|
||||
{
|
||||
0x5A827999,
|
||||
0x6ED9EBA1,
|
||||
0x8F1BBCDC,
|
||||
0xCA62C1D6
|
||||
};
|
||||
uint8_t t; /* Loop counter */
|
||||
uint32_t temp; /* Temporary word value */
|
||||
#ifdef METHOD2
|
||||
uint8_t s;
|
||||
uint32_t W[16];
|
||||
#else
|
||||
uint32_t W[80]; /* Word sequence */
|
||||
#endif
|
||||
uint32_t A, B, C, D, E; /* Word buffers */
|
||||
|
||||
/*
|
||||
* Initialize the first 16 words in the array W
|
||||
*/
|
||||
for (t = 0; t < 16; ++t)
|
||||
{
|
||||
W[t] = ((uint32_t)context->Message_Block[t * 4 + 0]) << 24;
|
||||
W[t] |= ((uint32_t)context->Message_Block[t * 4 + 1]) << 16;
|
||||
W[t] |= ((uint32_t)context->Message_Block[t * 4 + 2]) << 8;
|
||||
W[t] |= ((uint32_t)context->Message_Block[t * 4 + 3]) << 0;
|
||||
}
|
||||
|
||||
#ifndef METHOD2
|
||||
for (t = 16; t < 80; ++t)
|
||||
{
|
||||
W[t] = _circular_shift(1, (W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]));
|
||||
}
|
||||
#endif
|
||||
|
||||
A = context->Intermediate_Hash[0];
|
||||
B = context->Intermediate_Hash[1];
|
||||
C = context->Intermediate_Hash[2];
|
||||
D = context->Intermediate_Hash[3];
|
||||
E = context->Intermediate_Hash[4];
|
||||
|
||||
for (t = 0; t < 20; ++t)
|
||||
{
|
||||
#ifdef METHOD2
|
||||
s = t & 0x0f;
|
||||
if (t >= 16)
|
||||
{
|
||||
W[s] = _circular_shift(1, (W[(s + 13) & 0x0f] ^ W[(s + 8) & 0x0f] ^ W[(s + 2) & 0x0f] ^ W[s]));
|
||||
}
|
||||
temp = _circular_shift(5, A) + ((B & C) | ((~B) & D)) + E + W[s] + K[0];
|
||||
#else
|
||||
temp = _circular_shift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
|
||||
#endif
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for (t = 20; t < 40; ++t)
|
||||
{
|
||||
#ifdef METHOD2
|
||||
s = (t & 0x0f);
|
||||
W[s] = _circular_shift(1, (W[(s + 13) & 0x0f] ^ W[(s + 8) & 0x0f] ^ W[(s + 2) & 0x0f] ^ W[s]));
|
||||
temp = _circular_shift(5, A) + (B ^ C ^ D) + E + W[s] + K[1];
|
||||
#else
|
||||
temp = _circular_shift(5, A) + (B ^ C ^ D) + E + W[t] + K[1];
|
||||
#endif
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for (t = 40; t < 60; ++t)
|
||||
{
|
||||
#ifdef METHOD2
|
||||
s = (t & 0x0f);
|
||||
W[s] = _circular_shift(1, (W[(s + 13) & 0x0f] ^ W[(s + 8) & 0x0f] ^ W[(s + 2) & 0x0f] ^ W[s]));
|
||||
temp = _circular_shift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[s] + K[2];
|
||||
#else
|
||||
temp = _circular_shift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
|
||||
#endif
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for (t = 60; t < 80; ++t)
|
||||
{
|
||||
#ifdef METHOD2
|
||||
s = (t & 0x0f);
|
||||
W[s] = _circular_shift(1, (W[(s + 13) & 0x0f] ^ W[(s + 8) & 0x0f] ^ W[(s + 2) & 0x0f] ^ W[s]));
|
||||
temp = _circular_shift(5, A) + (B ^ C ^ D) + E + W[s] + K[3];
|
||||
#else
|
||||
temp = _circular_shift(5, A) + (B ^ C ^ D) + E + W[t] + K[3];
|
||||
#endif
|
||||
E = D;
|
||||
D = C;
|
||||
C = _circular_shift(30, B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
context->Intermediate_Hash[0] += A;
|
||||
context->Intermediate_Hash[1] += B;
|
||||
context->Intermediate_Hash[2] += C;
|
||||
context->Intermediate_Hash[3] += D;
|
||||
context->Intermediate_Hash[4] += E;
|
||||
|
||||
context->Message_Block_Index = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* _pad_block
|
||||
*
|
||||
* Description:
|
||||
* According to the standard, the message must be padded to an even
|
||||
* 512 bits. The first padding bit must be a '1'. The last 64
|
||||
* bits represent the length of the original message. All bits in
|
||||
* between should be 0. This function will pad the message
|
||||
* according to those rules by filling the Message_Block array
|
||||
* accordingly. It will also call the ProcessMessageBlock function
|
||||
* provided appropriately. When it returns, it can be assumed that
|
||||
* the message digest has been computed.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The context to pad
|
||||
* ProcessMessageBlock: [in]
|
||||
* The appropriate SHA*ProcessMessageBlock function
|
||||
* Returns:
|
||||
* Nothing.
|
||||
*
|
||||
*/
|
||||
static void _pad_block(struct sha1* context)
|
||||
{
|
||||
/*
|
||||
* Check to see if the current message block is too small to hold
|
||||
* the initial padding bits and length. If so, we will pad the
|
||||
* block, process it, and then continue padding into a second
|
||||
* block.
|
||||
*/
|
||||
if (context->Message_Block_Index > 55)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index] = 0x80;
|
||||
context->Message_Block_Index += 1;
|
||||
|
||||
while (context->Message_Block_Index < 64)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index] = 0;
|
||||
context->Message_Block_Index += 1;
|
||||
}
|
||||
|
||||
_process_block(context);
|
||||
|
||||
while (context->Message_Block_Index < 56)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index] = 0;
|
||||
context->Message_Block_Index += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index] = 0x80;
|
||||
context->Message_Block_Index += 1;
|
||||
|
||||
while (context->Message_Block_Index < 56)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index] = 0;
|
||||
context->Message_Block_Index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the message length as the last 8 bytes
|
||||
*/
|
||||
context->Message_Block[56] = context->Length_High >> 24;
|
||||
context->Message_Block[57] = context->Length_High >> 16;
|
||||
context->Message_Block[58] = context->Length_High >> 8;
|
||||
context->Message_Block[59] = context->Length_High >> 0;
|
||||
context->Message_Block[60] = context->Length_Low >> 24;
|
||||
context->Message_Block[61] = context->Length_Low >> 16;
|
||||
context->Message_Block[62] = context->Length_Low >> 8;
|
||||
context->Message_Block[63] = context->Length_Low >> 0;
|
||||
|
||||
_process_block(context);
|
||||
}
|
55
source/sha1.h
Normal file
55
source/sha1.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* sha1.h
|
||||
*
|
||||
* Description:
|
||||
* This is the header file for code which implements the Secure
|
||||
* Hashing Algorithm 1 as defined in FIPS PUB 180-1 published
|
||||
* April 17, 1995.
|
||||
*
|
||||
* Many of the variable names in this code, especially the
|
||||
* single character names, were used because those were the names
|
||||
* used in the publication.
|
||||
*
|
||||
* Please read the file sha1.c for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _SHA1_H_
|
||||
#define _SHA1_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SHA1HashSize 20
|
||||
|
||||
enum
|
||||
{
|
||||
shaSuccess = 0,
|
||||
shaNull, /* Null pointer parameter */
|
||||
shaInputTooLong, /* input data too long */
|
||||
shaStateError /* called Input after Result */
|
||||
};
|
||||
|
||||
#define FLAG_COMPUTED 1
|
||||
#define FLAG_CORRUPTED 2
|
||||
|
||||
/*
|
||||
* Data structure holding contextual information about the SHA-1 hash
|
||||
*/
|
||||
struct sha1
|
||||
{
|
||||
uint8_t Message_Block[64]; /* 512-bit message blocks */
|
||||
uint32_t Intermediate_Hash[5]; /* Message Digest */
|
||||
uint32_t Length_Low; /* Message length in bits */
|
||||
uint32_t Length_High; /* Message length in bits */
|
||||
uint16_t Message_Block_Index; /* Index into message block array */
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
int sha1_reset (struct sha1* context);
|
||||
int sha1_input (struct sha1* context, const uint8_t* message_array, unsigned length);
|
||||
int sha1_result(struct sha1* context, uint8_t Message_Digest[SHA1HashSize]);
|
||||
|
||||
#endif /* #ifndef _SHA1_H_ */
|
125
source/utils.c
Normal file
125
source/utils.c
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
static const int8_t base32_vals[256] = {
|
||||
// This map cheats and interprets:
|
||||
// - the numeral zero as the letter "O" as in oscar
|
||||
// - the numeral one as the letter "L" as in lima
|
||||
// - the numeral eight as the letter "B" as in bravo
|
||||
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x20
|
||||
14, 11, 26, 27, 28, 29, 30, 31, 1, -1, -1, -1, -1, 0, -1, -1, // 0x30
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x40
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 0x50
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x60
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, -1, -1, // 0x70
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x80
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x90
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xA0
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xB0
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xC0
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xD0
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xE0
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xF0
|
||||
};
|
||||
|
||||
int validate_b32key(char *k, size_t len, size_t pos)
|
||||
{
|
||||
// validates base32 key
|
||||
if (((len & 0xF) != 0) && ((len & 0xF) != 8))
|
||||
return 1;
|
||||
for (pos = 0; (pos < len); pos++) {
|
||||
if (base32_vals[k[pos]] == -1)
|
||||
return 1;
|
||||
if (k[pos] == '=') {
|
||||
if (((pos & 0xF) == 0) || ((pos & 0xF) == 8))
|
||||
return(1);
|
||||
if ((len - pos) > 6)
|
||||
return 1;
|
||||
switch (pos % 8) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 5:
|
||||
case 7:
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
for ( ; (pos < len); pos++) {
|
||||
if (k[pos] != '=')
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t decode_b32key(uint8_t **k, size_t len)
|
||||
{
|
||||
size_t keylen;
|
||||
size_t pos;
|
||||
// decodes base32 secret key
|
||||
keylen = 0;
|
||||
for (pos = 0; pos <= (len - 8); pos += 8) {
|
||||
// MSB is Most Significant Bits (0x80 == 10000000 ~= MSB)
|
||||
// MB is middle bits (0x7E == 01111110 ~= MB)
|
||||
// LSB is Least Significant Bits (0x01 == 00000001 ~= LSB)
|
||||
|
||||
// byte 0
|
||||
(*k)[keylen+0] = (base32_vals[(*k)[pos+0]] << 3) & 0xF8; // 5 MSB
|
||||
(*k)[keylen+0] |= (base32_vals[(*k)[pos+1]] >> 2) & 0x07; // 3 LSB
|
||||
if ((*k)[pos+2] == '=') {
|
||||
keylen += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// byte 1
|
||||
(*k)[keylen+1] = (base32_vals[(*k)[pos+1]] << 6) & 0xC0; // 2 MSB
|
||||
(*k)[keylen+1] |= (base32_vals[(*k)[pos+2]] << 1) & 0x3E; // 5 MB
|
||||
(*k)[keylen+1] |= (base32_vals[(*k)[pos+3]] >> 4) & 0x01; // 1 LSB
|
||||
if ((*k)[pos+4] == '=') {
|
||||
keylen += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// byte 2
|
||||
(*k)[keylen+2] = (base32_vals[(*k)[pos+3]] << 4) & 0xF0; // 4 MSB
|
||||
(*k)[keylen+2] |= (base32_vals[(*k)[pos+4]] >> 1) & 0x0F; // 4 LSB
|
||||
if ((*k)[pos+5] == '=') {
|
||||
keylen += 3;
|
||||
break;
|
||||
}
|
||||
|
||||
// byte 3
|
||||
(*k)[keylen+3] = (base32_vals[(*k)[pos+4]] << 7) & 0x80; // 1 MSB
|
||||
(*k)[keylen+3] |= (base32_vals[(*k)[pos+5]] << 2) & 0x7C; // 5 MB
|
||||
(*k)[keylen+3] |= (base32_vals[(*k)[pos+6]] >> 3) & 0x03; // 2 LSB
|
||||
if ((*k)[pos+7] == '=') {
|
||||
keylen += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// byte 4
|
||||
(*k)[keylen+4] = (base32_vals[(*k)[pos+6]] << 5) & 0xE0; // 3 MSB
|
||||
(*k)[keylen+4] |= (base32_vals[(*k)[pos+7]] >> 0) & 0x1F; // 5 LSB
|
||||
keylen += 5;
|
||||
}
|
||||
(*k)[keylen] = 0;
|
||||
|
||||
return keylen;
|
||||
}
|
26
source/utils.h
Normal file
26
source/utils.h
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
*
|
||||
* TOTP: Time-Based One-Time Password Algorithm
|
||||
* Copyright (c) 2017, fmount <fmount9@autistici.org>
|
||||
*
|
||||
* This software is distributed under MIT License
|
||||
*
|
||||
* Compute the hmac using openssl library.
|
||||
* SHA-1 engine is used by default, but you can pass another one,
|
||||
*
|
||||
* e.g EVP_md5(), EVP_sha224, EVP_sha512, etc
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _UTILS_H
|
||||
#define _UTILS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int validate_b32key(char *k, size_t len, size_t pos);
|
||||
size_t decode_b32key(uint8_t **k, size_t len);
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user