├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── icon.png ├── source ├── main.c └── timesync.c └── tinytot.smdh /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled executables # 2 | ######################## 3 | 4 | *.3dsx 5 | *.elf 6 | *.cia 7 | *.smdh 8 | 9 | # Object files # 10 | ################ 11 | 12 | *.o 13 | build 14 | 15 | # OS generated files # 16 | ###################### 17 | 18 | .DS_Store 19 | .DS_Store? 20 | ._* 21 | .Spotlight-V100 22 | .Trashes 23 | ehthumbs.db 24 | Thumbs.db 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | GIT_VERSION := $(shell git describe --long --always) 6 | 7 | ifeq ($(strip $(DEVKITARM)),) 8 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 9 | endif 10 | 11 | TOPDIR ?= $(CURDIR) 12 | include $(DEVKITARM)/3ds_rules 13 | 14 | #--------------------------------------------------------------------------------- 15 | # TARGET is the name of the output 16 | # BUILD is the directory where object files & intermediate files will be placed 17 | # SOURCES is a list of directories containing source code 18 | # DATA is a list of directories containing data files 19 | # INCLUDES is a list of directories containing header files 20 | # 21 | # NO_SMDH: if set to anything, no SMDH file is generated. 22 | # ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) 23 | # APP_TITLE is the name of the app stored in the SMDH file (Optional) 24 | # APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) 25 | # APP_AUTHOR is the author of the app stored in the SMDH file (Optional) 26 | # ICON is the filename of the icon (.png), relative to the project folder. 27 | # If not set, it attempts to use one of the following (in this order): 28 | # - .png 29 | # - icon.png 30 | # - /default_icon.png 31 | #--------------------------------------------------------------------------------- 32 | TARGET := $(notdir $(CURDIR)) 33 | BUILD := build 34 | SOURCES := source 35 | DATA := data 36 | INCLUDES := include 37 | APP_TITLE := tinytot 38 | APP_DESCRIPTION := Two-factor authentication OTP generator. 39 | APP_AUTHOR := Joshua Kelly 40 | #ROMFS := romfs 41 | 42 | #--------------------------------------------------------------------------------- 43 | # options for code generation 44 | #--------------------------------------------------------------------------------- 45 | ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft 46 | 47 | CFLAGS := -g -Wall -O2 -mword-relocations \ 48 | -fomit-frame-pointer -ffunction-sections \ 49 | $(ARCH) 50 | 51 | CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DAPP_VERSION="\"${GIT_VERSION}\"" -DAPP_TITLE="\"${APP_TITLE}\"" -DAPP_AUTHOR="\"${APP_AUTHOR}\"" 52 | 53 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 54 | 55 | ASFLAGS := -g $(ARCH) 56 | LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 57 | 58 | LIBS := -lctru -lm -loath 59 | 60 | #--------------------------------------------------------------------------------- 61 | # list of directories containing libraries, this must be the top level containing 62 | # include and lib 63 | #--------------------------------------------------------------------------------- 64 | LIBDIRS := $(CTRULIB) $(PORTLIBS) 65 | 66 | 67 | #--------------------------------------------------------------------------------- 68 | # no real need to edit anything past this point unless you need to add additional 69 | # rules for different file extensions 70 | #--------------------------------------------------------------------------------- 71 | ifneq ($(BUILD),$(notdir $(CURDIR))) 72 | #--------------------------------------------------------------------------------- 73 | 74 | export OUTPUT := $(CURDIR)/$(TARGET) 75 | export TOPDIR := $(CURDIR) 76 | 77 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 78 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 79 | 80 | export DEPSDIR := $(CURDIR)/$(BUILD) 81 | 82 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 83 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 84 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 85 | PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) 86 | SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) 87 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 88 | 89 | #--------------------------------------------------------------------------------- 90 | # use CXX for linking C++ projects, CC for standard C 91 | #--------------------------------------------------------------------------------- 92 | ifeq ($(strip $(CPPFILES)),) 93 | #--------------------------------------------------------------------------------- 94 | export LD := $(CC) 95 | #--------------------------------------------------------------------------------- 96 | else 97 | #--------------------------------------------------------------------------------- 98 | export LD := $(CXX) 99 | #--------------------------------------------------------------------------------- 100 | endif 101 | #--------------------------------------------------------------------------------- 102 | 103 | export OFILES := $(addsuffix .o,$(BINFILES)) \ 104 | $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ 105 | $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 106 | 107 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 108 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 109 | -I$(CURDIR)/$(BUILD) 110 | 111 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 112 | 113 | ifeq ($(strip $(ICON)),) 114 | icons := $(wildcard *.png) 115 | ifneq (,$(findstring $(TARGET).png,$(icons))) 116 | export APP_ICON := $(TOPDIR)/$(TARGET).png 117 | else 118 | ifneq (,$(findstring icon.png,$(icons))) 119 | export APP_ICON := $(TOPDIR)/icon.png 120 | endif 121 | endif 122 | else 123 | export APP_ICON := $(TOPDIR)/$(ICON) 124 | endif 125 | 126 | ifeq ($(strip $(NO_SMDH)),) 127 | export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh 128 | endif 129 | 130 | ifneq ($(ROMFS),) 131 | export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) 132 | endif 133 | 134 | .PHONY: $(BUILD) clean all 135 | 136 | #--------------------------------------------------------------------------------- 137 | all: $(BUILD) 138 | 139 | $(BUILD): 140 | @[ -d $@ ] || mkdir -p $@ 141 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 142 | 143 | #--------------------------------------------------------------------------------- 144 | clean: 145 | @echo clean ... 146 | @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf 147 | 148 | 149 | #--------------------------------------------------------------------------------- 150 | else 151 | 152 | DEPENDS := $(OFILES:.o=.d) 153 | 154 | #--------------------------------------------------------------------------------- 155 | # main targets 156 | #--------------------------------------------------------------------------------- 157 | ifeq ($(strip $(NO_SMDH)),) 158 | $(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh 159 | else 160 | $(OUTPUT).3dsx : $(OUTPUT).elf 161 | endif 162 | 163 | $(OUTPUT).elf : $(OFILES) 164 | 165 | #--------------------------------------------------------------------------------- 166 | # you need a rule like this for each extension you use as binary data 167 | #--------------------------------------------------------------------------------- 168 | %.bin.o : %.bin 169 | #--------------------------------------------------------------------------------- 170 | @echo $(notdir $<) 171 | @$(bin2o) 172 | 173 | #--------------------------------------------------------------------------------- 174 | # rules for assembling GPU shaders 175 | #--------------------------------------------------------------------------------- 176 | define shader-as 177 | $(eval CURBIN := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) 178 | picasso -o $(CURBIN) $1 179 | bin2s $(CURBIN) | $(AS) -o $@ 180 | echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h 181 | echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h 182 | echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h 183 | endef 184 | 185 | %.shbin.o : %.v.pica %.g.pica 186 | @echo $(notdir $^) 187 | @$(call shader-as,$^) 188 | 189 | %.shbin.o : %.v.pica 190 | @echo $(notdir $<) 191 | @$(call shader-as,$<) 192 | 193 | %.shbin.o : %.shlist 194 | @echo $(notdir $<) 195 | @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) 196 | 197 | -include $(DEPENDS) 198 | 199 | #--------------------------------------------------------------------------------------- 200 | endif 201 | #--------------------------------------------------------------------------------------- 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinytot 2 | ## Two-factor authentication app for the 3DS 3 | 4 | Currently only supports the TOTP algorithm. 5 | The secret to use should be stored in secret.txt in the same directory as the 3DSX, encoded in base 32. 6 | 7 | This is not at all related to the OTP files required for A9LH; sorry if I got your hopes up. 8 | This is used for generating One-Time Passwords for logging into websites, similar to Google Authenticator. 9 | 10 | ## License 11 | TinyTot is licensed under the Apache License v2.0. (see LICENSE.txt) 12 | Requires liboath from [oath-toolkit](http://www.nongnu.org/oath-toolkit/) by Simon Joseffson, et al., licensed under the LGPL. 13 | 14 | ## Compiling liboath 15 | Instructions for downloading & building liboath as a portlib (NOTE: these are quite outdated, and a PKGBUILD for dkp-pacman would be more appropriate, see issue #9) 16 | 17 | wget http://download.savannah.gnu.org/releases/oath-toolkit/oath-toolkit-2.6.1.tar.gz 18 | tar zxvf oath-toolkit-*.tar.gz 19 | cd oath-toolkit-*/liboath 20 | export PORTLIBS="${DEVKITPRO}/portlibs/armv6k" 21 | export PATH="${DEVKITARM}/bin:${PATH}" 22 | export CFLAGS="-march=armv6k -mtune=mpcore -mfloat-abi=hard -O3 -mword-relocations -fomit-frame-pointer -ffast-math" 23 | ./configure --prefix=${DEVKITPRO}/portlibs/armv6k --host=arm-none-eabi --disable-shared --enable-static 24 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejsa/tinytot/16542cdaf5544d7f2c4a26104499a586e56bcf5c/icon.png -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | #include <3ds.h> 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | char const * TEST_ENCODED_SECRET = "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"; /* secret (base32) */ 9 | 10 | // TODO: How to account for daylight savings time? 11 | // TODO: LibCtrU includes an internal function that appears to use a timezone: 12 | // int __libctru_gtod(struct _reent *ptr, struct timeval *tp, struct timezone *tz); 13 | // Is this exported anywhere? 14 | // If not, perhaps modify LibCtrU to include this functionality? 15 | 16 | // See libctru\include\3ds\result.h for definitions 17 | Result const R_TINYTOT_SUCCESS = MAKERESULT(RL_SUCCESS,RS_SUCCESS,RM_APPLICATION,RD_SUCCESS); 18 | Result const R_TINYTOT_OVERFLOW = MAKERESULT(RL_FATAL,RS_INVALIDARG,RM_APPLICATION,RD_TOO_LARGE); 19 | Result const R_TINYTOT_OUTOFMEMORY = MAKERESULT(RL_FATAL,RS_OUTOFRESOURCE,RM_APPLICATION,RD_OUT_OF_MEMORY); 20 | unsigned long const INVALID_DECODED_SECRET = 0; 21 | unsigned long const REQUESTED_OTP_DIGITS = 6; 22 | 23 | signed long const INVALID_CLOCK_OFFSET = 0x1FFFF; // illegal value... see InitializeClockOffset() 24 | signed long g_SystemClockUtcOffset = 0x1FFFF; // NULL for non-pointer isn't correct 25 | bool IsValidTimeOffset(u32 timeOffset) 26 | { 27 | // timeOffset valid values are +/- 12 hours (+/- 43200 seconds) 28 | u32 x = 43200; 29 | u32 y = 0 - x; 30 | return ((timeOffset <= x) || (timeOffset >= y)); 31 | } 32 | 33 | Result InitializeClockOffset() { 34 | /* Compares the system clock with current UTC time from timeapi.com */ 35 | /* ESSENTIAL for correct OTP generation, unless system clock is UTC */ 36 | /* Returns difference, in seconds. */ 37 | /* Add the result of this function to the 3DS time to get UTC. */ 38 | 39 | if (IsValidTimeOffset(g_SystemClockUtcOffset)) return true; 40 | 41 | Result ret = 0; 42 | unsigned long statusCode = 0; 43 | unsigned long contentSize = 0; 44 | unsigned char *buffer = NULL; 45 | 46 | httpcContext context; // NOTE: Uninitialized memory 47 | httpcInit(0); 48 | 49 | char * url = "http://old-labs.muffinti.me/atoolyoucanputonthewall.php"; 50 | /* put this line into a blank PHP file: 51 | 52 | 53 | 54 | */ 55 | 56 | /* URL returning current time in UTC */ 57 | 58 | if (ret) { 59 | ret = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1); 60 | } 61 | 62 | if (ret) { 63 | ret = httpcBeginRequest(&context); 64 | } 65 | 66 | if (ret) { 67 | ret = httpcGetResponseStatusCode(&context, &statusCode); 68 | if (ret && statusCode != 200) { 69 | printf("WARNING: HTTP status code returned was %d\n", statusCode); 70 | } 71 | } 72 | if (ret) { 73 | ret = httpcGetDownloadSizeState(&context, NULL, &contentSize); 74 | } 75 | if (ret) { 76 | if(contentSize+1 < contentSize) { 77 | ret = R_TINYTOT_OVERFLOW; // overflow -- do not allow 78 | } 79 | } 80 | if (ret) { 81 | buffer = (unsigned char*)malloc(contentSize+1); 82 | if(buffer == NULL) { 83 | ret = R_TINYTOT_OUTOFMEMORY; 84 | } 85 | } 86 | if (ret) { 87 | memset(buffer, 0, contentSize+1); // zero that last byte also 88 | ret = httpcDownloadData(&context, buffer, contentSize, NULL); 89 | } 90 | if (ret) { 91 | time_t utcTime = (time_t)atol(buffer); 92 | time_t systemTime = time(NULL); 93 | signed long timeDifference = systemTime - utcTime; /* time(NULL) + timeDifference = UTC */ 94 | g_SystemClockUtcOffset = timeDifference; 95 | } 96 | if (NULL != buffer) { 97 | free(buffer); 98 | } 99 | return ret; 100 | } 101 | 102 | // TODO: Change to accept a pointer, return Result type, so can call Initialize() directly here? 103 | unsigned long currentTimeUTC() { 104 | unsigned long systemTime = (unsigned long)time(NULL); 105 | return (unsigned long)(systemTime - g_SystemClockUtcOffset); 106 | } 107 | 108 | unsigned long generateTOTP(unsigned char const * secret, size_t const * secretLength) { 109 | if(*secretLength < 1) { 110 | printf("Secret is zero-length, cannot generate TOTP\n"); 111 | return INVALID_DECODED_SECRET; 112 | } 113 | 114 | if(!InitializeClockOffset) { 115 | printf("Failed to initializ clock offset, cannot generate TOTP\n"); 116 | return INVALID_DECODED_SECRET; 117 | } 118 | 119 | unsigned long timerightnow = currentTimeUTC(); 120 | unsigned char otp[REQUESTED_OTP_DIGITS+1]; /* must allocate for trailing NULL */ 121 | 122 | int ret = oath_totp_generate(secret, (size_t)*secretLength, timerightnow, OATH_TOTP_DEFAULT_TIME_STEP_SIZE, OATH_TOTP_DEFAULT_START_TIME, REQUESTED_OTP_DIGITS, (char*)&otp); 123 | if(ret != OATH_OK) { 124 | printf("Error generating TOTP: %s\n", oath_strerror(ret)); 125 | return INVALID_DECODED_SECRET; 126 | } 127 | 128 | return atol(otp); 129 | } 130 | 131 | static SwkbdCallbackResult swkbdCallbackThing(void* user, const char** ppMessage, const char* text, size_t textlen) 132 | { 133 | char *secret; 134 | signed short secretLength; 135 | 136 | int ret = oath_base32_decode(text, strlen(text), &secret, &secretLength); 137 | 138 | if(ret != OATH_OK) { 139 | printf("Error decoding secret: %s\n", oath_strerror(ret)); 140 | sprintf(*ppMessage, "Error decoding: %s", oath_strerror(ret)); 141 | return SWKBD_CALLBACK_CONTINUE; 142 | }else{ 143 | unsigned long otp = generateTOTP(secret, &secretLength); 144 | if(otp == 0) { 145 | *ppMessage = "OTP generation failed."; 146 | printf("\nOTP generation failed.\n"); 147 | return SWKBD_CALLBACK_CONTINUE; 148 | }else{ 149 | sprintf(*ppMessage, "OTP: %06lu", otp); 150 | printf("*** OTP: %06lu ***\n\n", otp); 151 | return SWKBD_CALLBACK_CONTINUE; 152 | } 153 | } 154 | 155 | return SWKBD_CALLBACK_OK; 156 | } 157 | 158 | int main() { 159 | gfxInitDefault(); 160 | consoleInit(GFX_BOTTOM, NULL); 161 | 162 | printf("%s %s by %s\n", APP_TITLE, APP_VERSION, APP_AUTHOR); 163 | printf("Build date: %s %s\n\n", __DATE__, __TIME__); 164 | //printf("Current time: %d\n\n", (int)time(NULL)); 165 | 166 | printf("Calculating time difference from UTC... make sure you are connected to the Internet. "); 167 | 168 | Result InitClockOffsetResult = InitializeClockOffset(); 169 | if (!InitClockOffsetResult) { 170 | printf("Error initializing time offset: %08x", InitClockOffsetResult); 171 | return 1; 172 | } 173 | printf("OK\n"); 174 | 175 | int ret = oath_init(); 176 | if(ret != OATH_OK) { 177 | printf("Error initializing liboath: %s\n", oath_strerror(ret)); 178 | return 1; 179 | } 180 | 181 | char encoded_secret[1024]; 182 | FILE *secretFile; 183 | signed short secretTxtDecLength; 184 | if((secretFile = fopen("secret.txt", "r")) == NULL) { 185 | printf("warning: Secret.txt not found in application directory (SD root if installed as CIA)"); 186 | /*while(aptMainLoop()) { 187 | gspWaitForVBlank(); 188 | hidScanInput(); 189 | 190 | unsigned long kDown = hidKeysDown(); 191 | if(kDown & KEY_A) break; 192 | gfxFlushBuffers(); 193 | gfxSwapBuffers(); 194 | } 195 | return 1;*/ 196 | }else{ 197 | printf("Opened secret.txt\n"); 198 | fscanf(secretFile, "%[^\n]", encoded_secret); 199 | fclose(secretFile); 200 | if(strlen(encoded_secret) < 1){ 201 | printf("warning: secret.txt exists but is empty.\n"); 202 | }else{ 203 | printf("Read secret.txt: %s\n", encoded_secret); 204 | 205 | ret = oath_base32_decode(&encoded_secret, strlen(&encoded_secret), NULL, &secretTxtDecLength); 206 | if(ret != OATH_OK) { 207 | printf("Error decoding secret.txt: %s\n", oath_strerror(ret)); 208 | memset(&encoded_secret[0], 0, sizeof(encoded_secret)); // wipe the copy we have in memory, to avoid prefilling the swkbd with undecodable stuff 209 | }else{ 210 | printf("Read secret.txt successfully."); 211 | } 212 | } 213 | } 214 | 215 | char inputSecret[1024]; 216 | 217 | bool quit = false; 218 | bool ask = false; 219 | 220 | printf("Press A to begin, or start to exit.\n\n"); 221 | 222 | // Main loop 223 | while (aptMainLoop()) 224 | { 225 | gspWaitForVBlank(); 226 | hidScanInput(); 227 | 228 | unsigned long kDown = hidKeysDown(); 229 | if (kDown & KEY_A) ask = true; 230 | 231 | if(ask && !quit) { 232 | static SwkbdState swkbd; 233 | static SwkbdStatusData swkbdStatus; 234 | SwkbdButton button = SWKBD_BUTTON_NONE; 235 | 236 | swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, 512); 237 | swkbdSetHintText(&swkbd, "Enter your TOTP secret."); 238 | swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Quit", false); 239 | swkbdSetButton(&swkbd, SWKBD_BUTTON_MIDDLE, "Load txt", true); 240 | swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Go", true); 241 | swkbdSetInitialText(&swkbd, inputSecret); 242 | swkbdSetFeatures(&swkbd, SWKBD_DEFAULT_QWERTY); 243 | swkbdSetFilterCallback(&swkbd, swkbdCallbackThing, NULL); 244 | 245 | swkbdInputText(&swkbd, inputSecret, sizeof(inputSecret)); 246 | 247 | switch(button) { 248 | case SWKBD_BUTTON_LEFT: // quit 249 | quit = true; 250 | break; 251 | case SWKBD_BUTTON_MIDDLE: // read secret.txt 252 | strcpy(inputSecret, encoded_secret); 253 | break; 254 | case SWKBD_BUTTON_RIGHT: // go (this is handled in filter callback) 255 | break; 256 | default: 257 | break; 258 | } 259 | 260 | if(quit) break; // quit to HBL 261 | } 262 | 263 | if (kDown & KEY_START) { 264 | break; // break in order to return to hbmenu 265 | } 266 | 267 | // Flush and swap framebuffers 268 | gfxFlushBuffers(); 269 | gfxSwapBuffers(); 270 | } 271 | 272 | gfxExit(); 273 | return 0; 274 | } 275 | -------------------------------------------------------------------------------- /source/timesync.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejsa/tinytot/16542cdaf5544d7f2c4a26104499a586e56bcf5c/source/timesync.c -------------------------------------------------------------------------------- /tinytot.smdh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejsa/tinytot/16542cdaf5544d7f2c4a26104499a586e56bcf5c/tinytot.smdh --------------------------------------------------------------------------------