summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile32
-rw-r--r--Makefile.MGW3240
-rw-r--r--crypto.cpp200
-rw-r--r--totpgen.h76
-rw-r--r--wxgui.cpp216
5 files changed, 564 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2a08b4e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+UNAME = $(shell uname)
+
+CXX = g++
+PREFIX = /usr
+WXCXXFLAGS = `wx-config --cxxflags`
+WXLDFLAGS = `wx-config --libs`
+
+ifeq ($(UNAME),FreeBSD)
+ CXX := clang++
+ PREFIX := /usr/local
+ WXCXXFLAGS := `wxgtk3u-3.2-config --cxxflags`
+ WXLDFLAGS := `wxgtk3u-3.2-config --libs`
+endif
+
+CXXFLAGS = -Wall -Wextra -Werror -std=c++11 -I. -I$(PREFIX)/include $(WXCXXFLAGS)
+LDFLAGS = -L$(PREFIX)/lib -lcrypto $(WXLDFLAGS)
+
+OUT = totpgen
+OBJS = wxgui.o crypto.o
+all: $(OBJS)
+ $(CXX) $(OBJS) -o $(OUT) $(LDFLAGS)
+
+crypto.o: crypto.cpp
+ $(CXX) $(CXXFLAGS) -c crypto.cpp -o crypto.o
+
+wxgui.o: wxgui.cpp
+ $(CXX) $(CXXFLAGS) -c wxgui.cpp -o wxgui.o
+
+.PHONY: clean
+clean:
+ rm -f *.o *.a */*.o */*.a $(OUT)
+
diff --git a/Makefile.MGW32 b/Makefile.MGW32
new file mode 100644
index 0000000..496b117
--- /dev/null
+++ b/Makefile.MGW32
@@ -0,0 +1,40 @@
+CXX = x86_64-w64-mingw32-c++
+AR = x86_64-w64-mingw32-ar
+CXX_LDD = x86_64-w64-mingw32-ld
+LDD = x86_64-w64-mingw32-ld
+
+WXINCLUDES = -Iwxmsw/include/x86_64-w64-mingw32-msw-unicode-3.2 -Iwxmsw/include
+WXCXXFLAGS = -D_FILE_OFFSET_BITS=64 -DWXUSINGDLL -D__WXMSW__ -pthread $(WXINCLUDES)
+
+CXXFLAGS = -static -std=c++11 -lwinpthread -I. -I/usr/lib/gcc/x86_64-w64-mingw32/12-win32/include $(WXCXXFLAGS)
+
+WIN32_DLL_DIR = win32_dlls
+
+MGWLIBS = $(wildcard $(WIN32_DLL_DIR)/libgcc*.dll) $(wildcard $(WIN32_DLL_DIR)/libstd*.dll)
+
+WXLIBS = $(wildcard $(WIN32_DLL_DIR)/wx*.dll)
+WXLDFLAGS = $(addprefix -l, $(basename $(WXLIBS)))
+
+SSLLIBS = $(wildcard $(WIN32_DLL_DIR)/libcrypto*.dll)
+SSLLDFLAGS = $(addprefix -l, $(basename $(SSLLIBS)))
+
+LDFLAGS = -L. -Wl,-Bdynamic $(WXLDFLAGS) $(SSLLDFLAGS)
+
+OUT = totpgen
+OBJS = wxgui.o crypto.o
+
+all: $(OUT)
+
+$(OUT): $(OBJS)
+ $(CXX) $(OBJS) -o $(OUT) $(LDFLAGS)
+ mkdir -p release && cp -r $(OUT).exe $(MGWLIBS) $(WXLIBS) $(SSLLIBS) release
+
+crypto.o: crypto.cpp
+ $(CXX) $(CXXFLAGS) -c crypto.cpp -o crypto.o
+
+wxgui.o: wxgui.cpp
+ $(CXX) $(CXXFLAGS) -c wxgui.cpp -o wxgui.o
+
+.PHONY: clean
+clean:
+ rm -f *.o *.a */*.o */*.a $(OUT).exe release/*
diff --git a/crypto.cpp b/crypto.cpp
new file mode 100644
index 0000000..818f68a
--- /dev/null
+++ b/crypto.cpp
@@ -0,0 +1,200 @@
+extern "C" {
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/aes.h>
+#include <openssl/rand.h>
+}
+
+#include "totpgen.h"
+
+#define UNUSED(x) (void(x))
+
+#define SALT_BYTES 8
+#define KEY_BYTES 32
+#define IV_BYTES 16
+
+
+#define AES_BUFFER(LEN) \
+ reinterpret_cast<unsigned char *>( \
+ calloc(AES_BLOCK_SIZE * (1 + ((LEN) / AES_BLOCK_SIZE)), \
+ sizeof(unsigned char)));
+
+bool
+TOTPGen::GenTOTP(std::string& secret_str)
+{
+ uint32_t bits, code, count, hmacsize;
+ uint64_t clock;
+ size_t length;
+ uint8_t msg[8], hmac[EVP_MAX_MD_SIZE];
+ char totp_buf[7];
+
+ size_t keysize = 0;
+ const EVP_MD *digest = EVP_sha1();
+ uint8_t *key = NULL;
+
+ char *secret = strdup(secret_str.c_str());
+
+ for (count = bits = length = 0; *secret; secret++) {
+ if (!strchr(base32.c_str(), *secret)) {
+ return false;
+ }
+ bits = (bits << 5) | (strchr(base32.c_str(), *secret) - base32.c_str());
+ count += 5;
+
+ while (count >= 8) {
+ while (length >= keysize) {
+ keysize = keysize ? keysize << 1 : 64;
+ if (!(key = (uint8_t *)realloc(key, keysize))) {
+ return false;
+ }
+ }
+ count -= 8, key[length++] = bits >> count;
+ }
+ }
+
+ if (length > 0) {
+ clock = time(NULL) / 30;
+
+ for (count = 0; count < 8; count++) {
+ msg[7 - count] = clock >> 8 * count;
+ }
+ if (!HMAC(digest, key, length, msg, sizeof(msg), hmac, &hmacsize)) {
+ return false;
+ }
+
+ for (code = count = 0; count < 4; count++) {
+ code += hmac[(hmac[hmacsize - 1] & 0x0f) + 3 - count] << 8 * count;
+ }
+ code &= 0x7fffffff;
+
+ snprintf((char *)&totp_buf, 7, "%06u", code % 1000000);
+ }
+ else {
+ return false;
+ }
+
+ TOTP = totp_buf;
+ TOTPField->SetLabel(TOTP);
+
+ free(key);
+
+ return true;
+}
+
+void
+TOTPGen::InitAES(const char *passphrase, unsigned char *salt,
+ unsigned char *key, unsigned char *iv)
+{
+ char *key_data = strdup(passphrase);
+ int key_text_len = strlen(passphrase);
+ int nrounds = 0xFF;
+
+ EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt,
+ reinterpret_cast<unsigned char *>(key_data),
+ key_text_len, nrounds, key, iv);
+
+ free(key_data);
+}
+
+bool
+TOTPGen::EncryptSecret(std::string& secret_str)
+{
+ unsigned char salt[SALT_BYTES], key[KEY_BYTES], iv[IV_BYTES];
+
+ RAND_bytes(salt, SALT_BYTES);
+
+ memset(&key, 0, KEY_BYTES);
+ memset(&iv, 0, IV_BYTES);
+
+ InitAES(Passphrase.c_str(), reinterpret_cast<unsigned char *>(&salt),
+ reinterpret_cast<unsigned char *>(&key),
+ reinterpret_cast<unsigned char *>(&iv));
+
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL,
+ reinterpret_cast<unsigned char *>(&key),
+ reinterpret_cast<unsigned char *>(&iv));
+
+ size_t plaintext_len = strlen(secret_str.c_str()) ;
+
+ unsigned char *plaintext = AES_BUFFER(plaintext_len);
+ strncpy(reinterpret_cast<char *>(plaintext),
+ secret_str.c_str(), plaintext_len);
+
+ unsigned char *ciphertext = AES_BUFFER(plaintext_len);
+
+ int len, ciphertext_len;
+ EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len);
+ ciphertext_len = len;
+ EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
+ ciphertext_len += len;
+
+ FILE *secret_file = fopen(SecretFile.c_str(), "wb");
+ if (!secret_file) {
+ return false;
+ }
+
+ fwrite(salt, SALT_BYTES, 1, secret_file);
+ fwrite(ciphertext, ciphertext_len, 1, secret_file);
+
+ fclose(secret_file);
+
+ EVP_CIPHER_CTX_free(ctx);
+ free(plaintext);
+ free(ciphertext);
+
+ return true;
+}
+
+
+bool
+TOTPGen::DecryptSecret(void)
+{
+ unsigned char salt[SALT_BYTES], key[KEY_BYTES], iv[IV_BYTES];
+
+ memset(&key, 0, KEY_BYTES);
+ memset(&iv, 0, IV_BYTES);
+
+ FILE *secret_file = fopen(SecretFile.c_str(), "rb");
+ if (!secret_file) {
+ return false;
+ }
+
+ fseek(secret_file, 0L, SEEK_END);
+ size_t ciphertext_len = ftell(secret_file) - SALT_BYTES;
+ rewind(secret_file);
+
+ fread(&salt, SALT_BYTES, 1, secret_file);
+
+ InitAES(Passphrase.c_str(), reinterpret_cast<unsigned char *>(&salt),
+ reinterpret_cast<unsigned char *>(&key),
+ reinterpret_cast<unsigned char *>(&iv));
+
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL,
+ reinterpret_cast<unsigned char *>(&key),
+ reinterpret_cast<unsigned char *>(&iv));
+
+ int len;
+ unsigned char *ciphertext = AES_BUFFER(ciphertext_len);
+ unsigned char *plaintext = AES_BUFFER(ciphertext_len + 8);
+
+ fread(ciphertext, 1, ciphertext_len, secret_file);
+ fclose(secret_file);
+
+ if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
+ return false;
+ }
+
+ if (!EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
+ return false;
+ }
+
+ Secret = reinterpret_cast<const char *>(plaintext);
+
+ EVP_CIPHER_CTX_free(ctx);
+ free(ciphertext);
+ free(plaintext);
+
+ return true;
+}
diff --git a/totpgen.h b/totpgen.h
new file mode 100644
index 0000000..f1a368d
--- /dev/null
+++ b/totpgen.h
@@ -0,0 +1,76 @@
+#include <iostream>
+#include <cstdio>
+#include <iomanip>
+#include <fstream>
+#include <string>
+#include <cstdlib>
+#include <ctime>
+#include <cstring>
+#include <cctype>
+#include <unordered_set>
+
+#include <unistd.h>
+
+#include <wx/wx.h>
+#include <wx/stdpaths.h>
+#include <wx/dir.h>
+#include <wx/clipbrd.h>
+#include <wx/regex.h>
+
+static const std::string base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+class TOTPGenGUI : public wxApp {
+ public:
+ virtual bool OnInit();
+};
+
+class TOTPGen : public wxFrame {
+ public:
+ TOTPGen();
+
+ private:
+ wxPanel *MainWindow;
+ wxBoxSizer *MainSizer;
+
+ wxBoxSizer *ButtonSizer_Upper;
+ wxBoxSizer *ButtonSizer_Lower;
+
+ wxButton *UpdateSecretButton;
+ void UpdateSecret(wxCommandEvent& event);
+
+ bool Locked;
+ wxButton *UnlockButton;
+ void Unlock(wxCommandEvent& event);
+
+ wxButton *CopyButton;
+ void CopyText(wxCommandEvent& event);
+
+ wxButton *QuitButton;
+ void OnExit(wxCommandEvent& event);
+
+ wxBoxSizer *TextSizer;
+ wxStaticText *TOTPField;
+
+ wxString SecretFile;
+
+ void InitAES(const char *passphrase,
+ unsigned char *salt,
+ unsigned char *key,
+ unsigned char *iv);
+
+ wxTimer *CountdownTimer;
+ wxGauge *CountdownGauge;
+ void UpdateStatus(wxTimerEvent& event);
+
+ std::string Secret;
+ std::string Passphrase;
+
+ bool IsBase32(std::string& str);
+
+ bool DecryptSecret(void);
+ bool EncryptSecret(std::string& secret_str);
+
+ std::string TOTP;
+
+ bool GenTOTP(std::string& secret_str);
+}; \ No newline at end of file
diff --git a/wxgui.cpp b/wxgui.cpp
new file mode 100644
index 0000000..6a6873a
--- /dev/null
+++ b/wxgui.cpp
@@ -0,0 +1,216 @@
+#include "totpgen.h"
+
+#ifdef _WIN32
+static const std::string path_separator = "\\";
+static const std::string config_dir_name = "TOTPGen";
+#else
+static const std::string path_separator = "/";
+static const std::string config_dir_name = ".totpgen";
+#endif
+
+IMPLEMENT_APP(TOTPGenGUI)
+
+bool TOTPGenGUI::OnInit()
+{
+ TOTPGen *MainAppFrame = new TOTPGen();
+ MainAppFrame->Show(true);
+ return true;
+}
+
+TOTPGen::TOTPGen() : wxFrame(nullptr, wxID_ANY, wxT("TOTP Generator"),
+ wxDefaultPosition, wxSize(320,240),
+ wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER)
+{
+ MainWindow = new wxPanel(this, wxID_ANY);
+ MainSizer = new wxBoxSizer(wxVERTICAL);
+
+ Locked = true;
+
+ wxString UserConfigDir = wxStandardPaths::Get().GetUserConfigDir();
+
+ wxString AppConfigDir = UserConfigDir + path_separator + config_dir_name;
+ if (!wxDirExists(AppConfigDir)) {
+ wxMkdir(AppConfigDir);
+ }
+
+ SecretFile = AppConfigDir + path_separator + "secret.dat";
+
+ TOTPField = new wxStaticText(MainWindow, wxID_ANY,
+ wxEmptyString, wxDefaultPosition,
+ wxDefaultSize, wxTE_CENTER);
+ wxFont LabelFont(48, wxFONTFAMILY_TELETYPE,
+ wxFONTSTYLE_NORMAL,
+ wxFONTWEIGHT_NORMAL);
+ TOTPField->SetFont(LabelFont);
+ TOTPField->SetLabel(" ");
+
+ TextSizer = new wxBoxSizer(wxHORIZONTAL);
+ TextSizer->Add(TOTPField, 2, wxCENTER);
+
+ CountdownGauge = new wxGauge(MainWindow, wxID_ANY, 30, wxPoint(50, 50));
+ CountdownTimer = new wxTimer(MainWindow, wxID_ANY);
+ CountdownTimer->Start(1000);
+
+ UpdateSecretButton = new wxButton(MainWindow, wxID_OK, wxT("Set Key"));
+ UpdateSecretButton->Bind(wxEVT_BUTTON, &TOTPGen::UpdateSecret, this);
+
+ UnlockButton = new wxButton(MainWindow, wxID_CLEAR, wxT("Unlock"));
+ UnlockButton->Bind(wxEVT_BUTTON, &TOTPGen::Unlock, this);
+ if (!wxFileExists(SecretFile)) {
+ UnlockButton->Enable(false);
+ }
+
+ ButtonSizer_Upper = new wxBoxSizer(wxHORIZONTAL);
+ ButtonSizer_Upper->Add(UpdateSecretButton, 2, wxEXPAND | wxALL);
+ ButtonSizer_Upper->Add(UnlockButton, 2, wxEXPAND | wxALL);
+
+ CopyButton = new wxButton(MainWindow, wxID_COPY, wxT("Copy"));
+ CopyButton->Bind(wxEVT_BUTTON, &TOTPGen::CopyText, this);
+ if (Locked) {
+ CopyButton->Enable(false);
+ }
+
+ QuitButton = new wxButton(MainWindow, wxID_EXIT, wxT("Exit"));
+ QuitButton->Bind(wxEVT_BUTTON, &TOTPGen::OnExit, this);
+
+ ButtonSizer_Lower = new wxBoxSizer(wxHORIZONTAL);
+ ButtonSizer_Lower->Add(CopyButton, 2, wxEXPAND | wxALL);
+ ButtonSizer_Lower->Add(QuitButton, 2, wxEXPAND | wxALL);
+
+ MainSizer->Add(TextSizer, 2, wxCENTER | wxALIGN_CENTER);
+ MainSizer->Add(CountdownGauge, 2, wxEXPAND | wxALL);
+ MainSizer->Add(ButtonSizer_Upper, 2, wxEXPAND | wxALL);
+ MainSizer->Add(ButtonSizer_Lower, 2, wxEXPAND | wxALL);
+
+ MainWindow->SetSizer(MainSizer);
+}
+
+void TOTPGen::OnExit(wxCommandEvent& WXUNUSED(event))
+{
+ Close(true);
+}
+
+bool TOTPGen::IsBase32(std::string& str)
+{
+ std::unordered_set<char> Base32Chars(base32.begin(), base32.end());
+
+ for (char c: str) {
+ if (Base32Chars.find(c) == Base32Chars.end()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void TOTPGen::UpdateSecret(wxCommandEvent& WXUNUSED(event))
+{
+ wxTextEntryDialog SecretPrompt(this, wxT("Please enter the base 32 token."),
+ wxT("Secret Entry"), wxEmptyString);
+
+ std::string Secret_New;
+ if (SecretPrompt.ShowModal() == wxID_OK) {
+ Secret_New = SecretPrompt.GetValue().MakeUpper().ToStdString();
+ if (!IsBase32(Secret_New)) {
+ wxMessageBox(wxT("Base 32 token contains invalid characters.\n"
+ "Only the following are permissible: \n" + base32),
+ wxT("Error"), wxOK | wxICON_ERROR);
+ return;
+ }
+ }
+
+ wxPasswordEntryDialog PasswordPrompt(this, wxT("Please enter a new passphrase\n"
+ "to encrypt the secret token."),
+ wxT("Passphrase Entry"), wxEmptyString);
+
+ wxPasswordEntryDialog ConfirmPrompt(this, wxT("Please confirm the passphrase."),
+ wxT("Passphrase Entry"), wxEmptyString);
+
+
+ if ((PasswordPrompt.ShowModal() == wxID_OK)
+ && (ConfirmPrompt.ShowModal() == wxID_OK)) {
+ if (PasswordPrompt.GetValue() == ConfirmPrompt.GetValue()) {
+ Passphrase = PasswordPrompt.GetValue();
+ }
+ else {
+ wxMessageBox(wxT("Passphrases did not match - try again"),
+ wxT("Error"), wxOK | wxICON_ERROR);
+ return;
+ }
+ }
+ else {
+ return;
+ }
+
+
+ if (!GenTOTP(Secret_New)) {
+ wxMessageBox(wxT("Could not generate TOTP - bad token?"),
+ wxT("Error"), wxOK | wxICON_ERROR);
+ return;
+ }
+
+ Secret = Secret_New;
+ EncryptSecret(Secret);
+
+ if (Locked) {
+ Locked = false;
+
+ MainWindow->Connect(CountdownTimer->GetId(), wxEVT_TIMER,
+ wxTimerEventHandler(TOTPGen::UpdateStatus),
+ NULL, this);
+
+ CopyButton->Enable(true);
+ UnlockButton->Enable(false);
+ }
+
+ return;
+}
+
+void TOTPGen::Unlock(wxCommandEvent& WXUNUSED(event))
+{
+ wxPasswordEntryDialog PasswordPrompt(this, wxT("Please enter your passphrase."),
+ wxT("Passphrase Entry"), wxEmptyString);
+
+ if (PasswordPrompt.ShowModal() == wxID_OK) {
+ Passphrase = PasswordPrompt.GetValue();
+ }
+
+ if (!DecryptSecret()) {
+ wxMessageBox(wxT("Could not unlock base 32 token,\n"
+ "bad passphrase or corrupt file?"),
+ wxT("Error"), wxOK | wxICON_ERROR);
+ return;
+ }
+
+ MainWindow->Connect(CountdownTimer->GetId(), wxEVT_TIMER,
+ wxTimerEventHandler(TOTPGen::UpdateStatus),
+ NULL, this);
+
+ Locked = false;
+
+ CopyButton->Enable(true);
+ UnlockButton->Enable(false);
+
+ GenTOTP(Secret);
+}
+
+void TOTPGen::UpdateStatus(wxTimerEvent& WXUNUSED(event))
+{
+ unsigned char CounterValue = 29 - ((time(NULL) - 1) % 30);
+
+ CountdownGauge->SetValue(CounterValue);
+
+ if (CounterValue == 0) {
+ GenTOTP(Secret);
+ }
+}
+
+void TOTPGen::CopyText(wxCommandEvent& WXUNUSED(event))
+{
+ wxString ToCopy = TOTPField->GetLabel();
+
+ if (wxTheClipboard->Open() && !Locked) {
+ wxTheClipboard->SetData(new wxTextDataObject(ToCopy));
+ wxTheClipboard->Close();
+ }
+} \ No newline at end of file