#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 | wxMAXIMIZE_BOX)) { 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(" "); ResizeTextToFit(TOTPField); 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 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 == true) { 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(); } } void TOTPGen::ResizeTextToFit(wxStaticText* text_field) { wxString text = text_field->GetLabel(); wxSize text_size = text_field->GetClientSize(); #if defined(__WXGTK__) double GTK_scale = std::stod(getenv("GDK_DPI_SCALE")); text_size.x /= GTK_scale; text_size.y /= GTK_scale; #endif wxFont font = text_field->GetFont(); int font_size = font.GetPointSize(); wxClientDC device_ctx(text_field); device_ctx.SetFont(font); int text_width, text_height; device_ctx.GetTextExtent(text, &text_width, &text_height); while (text_width > (text_size.x - 12) || text_height > (text_size.y - 12)) { font_size--; font.SetPointSize(font_size); device_ctx.SetFont(font); device_ctx.GetTextExtent(text, &text_width, &text_height); } text_field->SetFont(font); text_field->Refresh(); }