mirror of
https://github.com/rwinkhart/sshyp-labs.git
synced 2026-08-28 04:46:48 -04:00
Re-organized repo for upcoming v1.5.0 extension management changes
This commit is contained in:
@@ -1,20 +1,17 @@
|
|||||||
# sshyp-labs
|
# sshyp-labs
|
||||||
Experimental extensions for the sshyp password manager.
|
Extensions for the [sshyp password manager](https://github.com/rwinkhart/sshyp).
|
||||||
|
|
||||||
sshyp-labs is currently the home of sshyp-mfa and password-pasture (sshyp-gui).
|
When new functionality is desired that goes outside of sshyp's primary goals or requires venturing outside of the Python standard library, said functionality is implemented as an extension.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
For sshyp v1.5.0+, sshyp extensions should be installed from the `sshyp tweak` menu's "extension management" option.
|
||||||
|
|
||||||
|
**Do NOT use the packages from the releases page! These are only for older, unsupported releases of sshyp!**
|
||||||
|
|
||||||
# Available Extensions
|
# Available Extensions
|
||||||
sshyp-mfa - [installation and usage instructions](https://github.com/rwinkhart/sshyp-labs/wiki/sshyp-mfa)
|
[sshyp-mfa](https://github.com/rwinkhart/sshyp-labs/wiki/sshyp-mfa): read mfa data from sshyp entries to generate and copy totp keys to the clipboard (optional Steam support)
|
||||||
|
|
||||||
sshyp-mfa is a unique approach to generating multi-factor authentication keys. Upon running `sshyp </entry name> copy -m`,
|
[password-pasture](https://github.com/rwinkhart/sshyp-labs/wiki/password-pasture): a HIGHLY experimental GTK4 sshyp GUI - very incomplete (last updated for sshyp v1.1.x)
|
||||||
an MFA key will be generated and copied to your clipboard. sshyp-mfa will continue to run in the background and copy a
|
|
||||||
new key to your clipboard every time the actively copied one expires. Never worry about a TOTP timer expiring again!
|
|
||||||
At any time, sshyp-mfa can be closed with ctrl+c to stop this process.
|
|
||||||
|
|
||||||
password-pasture - [installation and usage instructions](https://github.com/rwinkhart/sshyp-labs/wiki/password-pasture)
|
|
||||||
|
|
||||||
password-pasture is an incredibly experimental GTK4 GUI interface for sshyp. It is currently not in a usable state
|
|
||||||
and has not been updated since sshyp v1.1.X. Development is set to resume after sshyp is more feature-complete.
|
|
||||||
|
|
||||||
# Acknowledgements
|
# Acknowledgements
|
||||||
sshyp-mfa relies on [ValvePython/steam](https://github.com/ValvePython/steam) for Steam support.
|
sshyp-mfa relies on [ValvePython/steam](https://github.com/ValvePython/steam) for Steam support.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
git add -f extra password-pasture sshyp-mfa LICENSE README.md version commit.sh .gitignore
|
git add -f LICENSE README.md commit.sh extensions extra pointers
|
||||||
git commit -m "$1"
|
git commit -m "$1"
|
||||||
git push
|
git push
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.23)
|
||||||
|
project(password_pasture C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 17)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK4 REQUIRED gtk4)
|
||||||
|
|
||||||
|
include_directories(${GTK4_INCLUDE_DIRS})
|
||||||
|
link_directories(${GTK4_LIBRARY_DIRS})
|
||||||
|
|
||||||
|
add_definitions(${GTK4_CFLAGS_OTHER})
|
||||||
|
|
||||||
|
add_executable(password_pasture main.c)
|
||||||
|
|
||||||
|
target_link_libraries(password_pasture ${GTK4_LIBRARIES})
|
||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
rm -rf CMakeFiles cmake_install.cmake CMakeCache.txt Makefile
|
||||||
|
cmake ./
|
||||||
|
sleep 10 # prevents clock skew
|
||||||
|
make -j$(nproc)
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
GtkWidget *label_selected; GtkListBox *list_box;
|
||||||
|
char entries[1024][1024]; int entry_array_count = 0;
|
||||||
|
|
||||||
|
void str_remove(char *str, const char *sub) {
|
||||||
|
char *p, *q, *r;
|
||||||
|
if (*sub && (q = r = strstr(str, sub)) != 0) {
|
||||||
|
size_t len = strlen(sub);
|
||||||
|
while ((r = strstr(p = r + len, sub)) != 0) {
|
||||||
|
while (p < r)
|
||||||
|
*q++ = *p++;
|
||||||
|
}
|
||||||
|
while ((*q++ = *p++) != '\0')
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gen_entry_array(char *path, size_t size, const char *path_orig) {
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *entry;
|
||||||
|
size_t len = strlen(path);
|
||||||
|
|
||||||
|
if (!(dir = opendir(path))) {
|
||||||
|
return; }
|
||||||
|
|
||||||
|
while ((entry = readdir(dir)) != 0) {
|
||||||
|
char *name = entry->d_name;
|
||||||
|
if (entry->d_type == DT_DIR) {
|
||||||
|
if (!strcmp(name, ".") || !strcmp(name, "..")) {
|
||||||
|
continue; }
|
||||||
|
path[len] = '/';
|
||||||
|
strcpy(path + len + 1, name);
|
||||||
|
gen_entry_array(path, size, path_orig);
|
||||||
|
path[len] = '\0';
|
||||||
|
} else {
|
||||||
|
char path_filtered[1024];
|
||||||
|
strcpy(path_filtered, path);
|
||||||
|
str_remove(path_filtered, path_orig);
|
||||||
|
size_t name_len = strlen(name);
|
||||||
|
name[name_len-4] = 0;
|
||||||
|
char sshyp_path[1024]; strcpy(sshyp_path, path_filtered); strcat(sshyp_path, "/"); strcat(sshyp_path, name);
|
||||||
|
strcpy(entries[entry_array_count], sshyp_path);
|
||||||
|
entry_array_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void password_convert(GtkEntryBuffer *buffer) {
|
||||||
|
const char *password = gtk_entry_buffer_get_text(buffer);
|
||||||
|
char *sub1 = "gpg --pinentry-mode loopback --batch --passphrase-fd 0 --armor -qd --output /dev/null ~/.config/sshyp/lock.gpg <<< '", *sub2 = "'", *command = (char *) malloc(2048);
|
||||||
|
strcpy(command, sub1); strcat(command, password); strcat(command, sub2);
|
||||||
|
system(command);
|
||||||
|
free(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gpg_lock() {
|
||||||
|
system("gpgconf --reload gpg-agent");
|
||||||
|
}
|
||||||
|
|
||||||
|
void password_prompt(GtkWindow *window) {
|
||||||
|
GtkWidget *dialog, *dialog_header, *box_header; // main window and header bar
|
||||||
|
GtkWidget *button_unlock, *button_cancel, *button_lock; // header bar buttons
|
||||||
|
GtkWidget *content_area, *password_entry; // content area and widgets
|
||||||
|
|
||||||
|
dialog = gtk_dialog_new();
|
||||||
|
password_entry = gtk_entry_new();
|
||||||
|
gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);
|
||||||
|
gtk_entry_set_placeholder_text(GTK_ENTRY(password_entry), "passphrase");
|
||||||
|
dialog_header = gtk_header_bar_new();
|
||||||
|
gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(dialog_header), FALSE);
|
||||||
|
box_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
gtk_widget_set_halign(box_header, GTK_ALIGN_CENTER);
|
||||||
|
button_unlock = gtk_button_new_with_label("unlock");
|
||||||
|
button_cancel = gtk_button_new_with_label("cancel");
|
||||||
|
button_lock = gtk_button_new_from_icon_name("changes-prevent");
|
||||||
|
gtk_widget_set_size_request(button_lock, 20, -1);
|
||||||
|
gtk_box_append(GTK_BOX(box_header), button_unlock);
|
||||||
|
gtk_box_append(GTK_BOX(box_header), button_cancel);
|
||||||
|
gtk_box_append(GTK_BOX(box_header), button_lock);
|
||||||
|
gtk_header_bar_set_title_widget(GTK_HEADER_BAR(dialog_header), box_header);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(dialog), "");
|
||||||
|
gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window));
|
||||||
|
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(dialog), 50, 50);
|
||||||
|
|
||||||
|
// add password entry to dialog content area
|
||||||
|
content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
gtk_box_append(GTK_BOX(content_area), password_entry);
|
||||||
|
|
||||||
|
gtk_window_set_titlebar(GTK_WINDOW(dialog), dialog_header);
|
||||||
|
gtk_widget_show(dialog);
|
||||||
|
|
||||||
|
// button actions
|
||||||
|
g_signal_connect_swapped(button_unlock, "clicked", G_CALLBACK(password_convert), gtk_entry_get_buffer(GTK_ENTRY(password_entry)));
|
||||||
|
g_signal_connect_swapped(button_unlock, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_cancel, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect(button_lock, "clicked", G_CALLBACK(gpg_lock), 0);
|
||||||
|
g_signal_connect_swapped(button_lock, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_item(GtkWidget *button) {
|
||||||
|
char *command = malloc(2048); const char *button_label = gtk_button_get_label(GTK_BUTTON(button));
|
||||||
|
strcpy(command, "sshyp copy ");
|
||||||
|
strcat(command, button_label); strcat(command, " ");
|
||||||
|
strcat(command, gtk_label_get_text((GtkLabel *) label_selected));
|
||||||
|
system(command);
|
||||||
|
free(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_edit_field_prompt(GtkWidget *button, gpointer *window) {
|
||||||
|
GtkWidget *dialog, *dialog_header; // main window and header bar
|
||||||
|
GtkWidget *button_cancel; // header bar buttons
|
||||||
|
GtkWidget *content_area, *button_pwd, *button_usr, *button_url, *button_nte, *button_mfa; // content area and widgets
|
||||||
|
int mode; // copy/edit mode flag
|
||||||
|
|
||||||
|
// set mode based on button pressed, copy = 1, edit = 2
|
||||||
|
const char *button_label = gtk_button_get_icon_name(GTK_BUTTON(button));
|
||||||
|
if (strcmp(button_label, "edit-copy") == 0) {
|
||||||
|
mode = 1;
|
||||||
|
} else {
|
||||||
|
mode = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog = gtk_dialog_new();
|
||||||
|
dialog_header = gtk_header_bar_new();
|
||||||
|
gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(dialog_header), FALSE);
|
||||||
|
button_cancel = gtk_button_new_with_label("cancel");
|
||||||
|
gtk_header_bar_set_title_widget(GTK_HEADER_BAR(dialog_header), button_cancel);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(dialog), "");
|
||||||
|
gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window));
|
||||||
|
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(dialog), 50, 50);
|
||||||
|
|
||||||
|
// add options to dialog content area
|
||||||
|
content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
button_pwd = gtk_button_new_with_label("password");
|
||||||
|
button_usr = gtk_button_new_with_label("username");
|
||||||
|
button_url = gtk_button_new_with_label("url");
|
||||||
|
button_nte = gtk_button_new_with_label("note");
|
||||||
|
button_mfa = gtk_button_new_with_label("mfa");
|
||||||
|
gtk_box_append(GTK_BOX(content_area), button_pwd);
|
||||||
|
gtk_box_append(GTK_BOX(content_area), button_usr);
|
||||||
|
gtk_box_append(GTK_BOX(content_area), button_url);
|
||||||
|
gtk_box_append(GTK_BOX(content_area), button_nte);
|
||||||
|
gtk_box_append(GTK_BOX(content_area), button_mfa);
|
||||||
|
|
||||||
|
gtk_window_set_titlebar(GTK_WINDOW(dialog), dialog_header);
|
||||||
|
gtk_widget_show(dialog);
|
||||||
|
|
||||||
|
// button actions
|
||||||
|
if (mode == 1) {
|
||||||
|
g_signal_connect(button_pwd, "clicked", G_CALLBACK(copy_item), 0);
|
||||||
|
g_signal_connect(button_usr, "clicked", G_CALLBACK(copy_item), 0);
|
||||||
|
g_signal_connect(button_url, "clicked", G_CALLBACK(copy_item), 0);
|
||||||
|
g_signal_connect(button_nte, "clicked", G_CALLBACK(copy_item), 0);
|
||||||
|
g_signal_connect(button_mfa, "clicked", G_CALLBACK(copy_item), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_connect_swapped(button_cancel, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_pwd, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_usr, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_url, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_nte, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_mfa, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_selection_label(GtkWidget *button, gpointer *label) {
|
||||||
|
const char *button_label = gtk_button_get_label(GTK_BUTTON(button));
|
||||||
|
gtk_label_set_text(GTK_LABEL(label), button_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void entry_list_gen() {
|
||||||
|
GtkWidget *entry_button;
|
||||||
|
|
||||||
|
GtkWidget *iter = gtk_widget_get_first_child ((GtkWidget *) list_box);
|
||||||
|
while (iter != 0) {
|
||||||
|
GtkWidget *next = gtk_widget_get_next_sibling (iter);
|
||||||
|
gtk_list_box_remove (list_box, iter);
|
||||||
|
iter = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[1024]; char *user_home = getenv("HOME"); strcpy(path, user_home); strcat(path, "/.local/share/sshyp");
|
||||||
|
char path_orig[1024]; strcpy(path_orig, path); strcat(path_orig, "/");
|
||||||
|
gen_entry_array(path, sizeof path, path_orig);
|
||||||
|
|
||||||
|
// bubble sort entries array
|
||||||
|
char *temp = (char *) malloc(1024);
|
||||||
|
for(int i=0; i<entry_array_count; i++){
|
||||||
|
for(int j=0; j<entry_array_count-1-i; j++){
|
||||||
|
if(strcmp(entries[j], entries[j+1]) > 0){
|
||||||
|
strcpy(temp, entries[j]);
|
||||||
|
strcpy(entries[j], entries[j+1]);
|
||||||
|
strcpy(entries[j+1], temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(temp);
|
||||||
|
|
||||||
|
strcpy(entries[entry_array_count], "\0");
|
||||||
|
|
||||||
|
for (int i_2 = 0; strcmp(entries[i_2], "\0") != 0; i_2++) {
|
||||||
|
entry_button = gtk_button_new_with_label(entries[i_2]);
|
||||||
|
gtk_label_set_xalign(GTK_LABEL(gtk_button_get_child(GTK_BUTTON(entry_button))), GTK_JUSTIFY_LEFT);
|
||||||
|
gtk_button_set_has_frame(GTK_BUTTON(entry_button), FALSE);
|
||||||
|
g_signal_connect(entry_button, "clicked", G_CALLBACK(set_selection_label), (gpointer *)label_selected);
|
||||||
|
gtk_list_box_append(list_box, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||||
|
gtk_list_box_append(list_box, entry_button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sshyp_sync() {
|
||||||
|
system("sshyp sync");
|
||||||
|
entry_array_count = 0;
|
||||||
|
entry_list_gen();
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete_entry() {
|
||||||
|
char *command = (char *) malloc(2048);
|
||||||
|
strcpy(command, "sshyp shear "); strcat(command, gtk_label_get_text(GTK_LABEL(label_selected)));
|
||||||
|
system(command);
|
||||||
|
free(command);
|
||||||
|
entry_array_count = 0;
|
||||||
|
entry_list_gen();
|
||||||
|
}
|
||||||
|
|
||||||
|
void are_they_sure(GtkWindow *window) {
|
||||||
|
GtkWidget *dialog, *dialog_header, *box_header; // main window and header bar
|
||||||
|
GtkWidget *button_delete, *button_cancel; // header bar buttons
|
||||||
|
GtkWidget *content_area, *label_sure; // content area and widgets
|
||||||
|
|
||||||
|
dialog = gtk_dialog_new();
|
||||||
|
dialog_header = gtk_header_bar_new();
|
||||||
|
gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(dialog_header), FALSE);
|
||||||
|
box_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
gtk_widget_set_halign(box_header, GTK_ALIGN_CENTER);
|
||||||
|
button_delete = gtk_button_new_with_label("delete");
|
||||||
|
button_cancel = gtk_button_new_with_label("cancel");
|
||||||
|
gtk_box_append(GTK_BOX(box_header), button_delete);
|
||||||
|
gtk_box_append(GTK_BOX(box_header), button_cancel);
|
||||||
|
gtk_header_bar_set_title_widget(GTK_HEADER_BAR(dialog_header), box_header);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(dialog), "");
|
||||||
|
gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window));
|
||||||
|
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(dialog), 50, 50);
|
||||||
|
|
||||||
|
// add label to dialog content area
|
||||||
|
content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
gtk_widget_set_halign(content_area, GTK_ALIGN_CENTER);
|
||||||
|
char *label_text = malloc(2048);
|
||||||
|
strcpy(label_text, "this will delete:\n");
|
||||||
|
strcat(label_text, gtk_label_get_text(GTK_LABEL(label_selected)));
|
||||||
|
strcat(label_text, "\nare you sure?");
|
||||||
|
label_sure = gtk_label_new(label_text);
|
||||||
|
free(label_text);
|
||||||
|
gtk_label_set_justify(GTK_LABEL(label_sure), GTK_JUSTIFY_CENTER);
|
||||||
|
gtk_box_append(GTK_BOX(content_area), label_sure);
|
||||||
|
|
||||||
|
// button actions
|
||||||
|
g_signal_connect(button_delete, "clicked", G_CALLBACK(delete_entry), 0);
|
||||||
|
g_signal_connect_swapped(button_delete, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
g_signal_connect_swapped(button_cancel, "clicked", G_CALLBACK(gtk_window_close), dialog);
|
||||||
|
|
||||||
|
gtk_window_set_titlebar(GTK_WINDOW(dialog), dialog_header);
|
||||||
|
gtk_widget_show(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void activate(GtkApplication* app) {
|
||||||
|
GtkWidget *window, *header_bar; // main window and header bar
|
||||||
|
GtkWidget *button_sync, *button_unlock; // header bar buttons
|
||||||
|
GtkWidget *stack, *stack_switcher; GtkStackPage *page_info; // stack widgets
|
||||||
|
GtkWidget *box_info, *sshyp_logo, *label_home1; // info page widgets
|
||||||
|
GtkWidget *box_browse_page, *box_browse_controls; // browse page boxes
|
||||||
|
GtkWidget *button_shear, *button_read, *button_edit, *button_copy; // browse buttons
|
||||||
|
GtkWidget *scrollable; // scrollable entry list container
|
||||||
|
|
||||||
|
window = gtk_application_window_new(app);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(window), "");
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(window), 360, 720);
|
||||||
|
|
||||||
|
header_bar = gtk_header_bar_new();
|
||||||
|
gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(header_bar), FALSE);
|
||||||
|
|
||||||
|
// !!start main stack stuff!!
|
||||||
|
|
||||||
|
// create the stack
|
||||||
|
stack = gtk_stack_new();
|
||||||
|
gtk_stack_set_transition_type(GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
|
||||||
|
|
||||||
|
// attach the stack to the main window
|
||||||
|
gtk_window_set_child(GTK_WINDOW(window), stack);
|
||||||
|
|
||||||
|
// browse
|
||||||
|
button_shear = gtk_button_new_from_icon_name("edit-delete");
|
||||||
|
gtk_widget_set_hexpand(button_shear, FALSE);
|
||||||
|
gtk_widget_set_size_request(button_shear, 50, -1);
|
||||||
|
g_signal_connect_swapped(button_shear, "clicked", G_CALLBACK(are_they_sure), window);
|
||||||
|
button_read = gtk_button_new_from_icon_name("mail-read");
|
||||||
|
gtk_widget_set_hexpand(button_read, TRUE);
|
||||||
|
button_edit = gtk_button_new_from_icon_name("document-edit");
|
||||||
|
gtk_widget_set_hexpand(button_edit, TRUE);
|
||||||
|
g_signal_connect(button_edit, "clicked", G_CALLBACK(copy_edit_field_prompt), (gpointer *) window);
|
||||||
|
button_copy = gtk_button_new_from_icon_name("edit-copy");
|
||||||
|
gtk_widget_set_hexpand(button_copy, TRUE);
|
||||||
|
g_signal_connect(button_copy, "clicked", G_CALLBACK(copy_edit_field_prompt), (gpointer *) window);
|
||||||
|
box_browse_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
label_selected = gtk_label_new("no entry selected");
|
||||||
|
gtk_widget_set_hexpand(label_selected, TRUE);
|
||||||
|
gtk_widget_set_halign(label_selected, GTK_ALIGN_CENTER);
|
||||||
|
box_browse_controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
|
gtk_widget_set_valign(box_browse_controls, GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_size_request(box_browse_controls, -1, 50);
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_controls), button_shear);
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_controls), button_read);
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_controls), button_edit);
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_controls), button_copy);
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_page), label_selected);
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_page), box_browse_controls);
|
||||||
|
|
||||||
|
// !!start scrollable list!!
|
||||||
|
scrollable = gtk_scrolled_window_new();
|
||||||
|
list_box = (GtkListBox *) gtk_list_box_new();
|
||||||
|
gtk_widget_set_vexpand(GTK_WIDGET(list_box), TRUE);
|
||||||
|
|
||||||
|
entry_list_gen();
|
||||||
|
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollable), GTK_WIDGET(list_box));
|
||||||
|
// !!end scrollable list!!
|
||||||
|
|
||||||
|
gtk_box_append(GTK_BOX(box_browse_page), GTK_WIDGET(scrollable));
|
||||||
|
|
||||||
|
// info
|
||||||
|
box_info = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||||
|
sshyp_logo = gtk_image_new_from_file("PLACEHOLDER");
|
||||||
|
gtk_image_set_pixel_size(GTK_IMAGE(sshyp_logo), 300);
|
||||||
|
label_home1 = gtk_label_new("password pasture\nexperimental gui client for sshyp");
|
||||||
|
gtk_label_set_justify(GTK_LABEL(label_home1), GTK_JUSTIFY_CENTER);
|
||||||
|
gtk_label_set_markup(GTK_LABEL(label_home1), "<span size='large'><b>password pasture</b></span>\n<span size='small'>experimental gui client for sshyp</span>");
|
||||||
|
gtk_box_append(GTK_BOX(box_info), sshyp_logo);
|
||||||
|
gtk_box_append(GTK_BOX(box_info), label_home1);
|
||||||
|
|
||||||
|
stack_switcher = gtk_stack_switcher_new();
|
||||||
|
page_info = gtk_stack_add_titled(GTK_STACK(stack), box_info, "info", "info");
|
||||||
|
gtk_stack_page_set_icon_name(GTK_STACK_PAGE(page_info), "help-about");
|
||||||
|
gtk_stack_add_titled(GTK_STACK(stack), box_browse_page, "browse", "browse");
|
||||||
|
gtk_stack_switcher_set_stack(GTK_STACK_SWITCHER(stack_switcher), GTK_STACK(stack));
|
||||||
|
gtk_header_bar_pack_start(GTK_HEADER_BAR(header_bar), stack_switcher);
|
||||||
|
|
||||||
|
// !!end main stack stuff!!
|
||||||
|
|
||||||
|
// header bar buttons
|
||||||
|
button_sync = gtk_button_new_from_icon_name("view-refresh");
|
||||||
|
g_signal_connect(button_sync, "clicked", G_CALLBACK(sshyp_sync), 0);
|
||||||
|
gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button_sync);
|
||||||
|
|
||||||
|
button_unlock = gtk_button_new_from_icon_name("changes-allow");
|
||||||
|
g_signal_connect_swapped(button_unlock, "clicked", G_CALLBACK(password_prompt), window);
|
||||||
|
gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button_unlock);
|
||||||
|
|
||||||
|
//GtkWidget *button_debug = gtk_button_new_from_icon_name("applications-science");
|
||||||
|
//g_signal_connect(button_debug, "clicked", G_CALLBACK(0), 0);
|
||||||
|
//gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button_debug);
|
||||||
|
|
||||||
|
gtk_window_set_titlebar(GTK_WINDOW(window), header_bar);
|
||||||
|
gtk_widget_show(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc,
|
||||||
|
char **argv) {
|
||||||
|
GtkApplication *app = gtk_application_new("org.rwinkhart.sshyp", G_APPLICATION_FLAGS_NONE);
|
||||||
|
g_signal_connect(app, "activate", G_CALLBACK(activate), 0);
|
||||||
|
int status = g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
g_object_unref(app);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
arch: base-devel gtk4 cmake make gcc
|
||||||
|
|
||||||
|
alpine: build-base gtk4.0 gtk4.0-dev cmake make gcc binutils abuild
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[config]
|
||||||
|
input = copy -m
|
||||||
|
output = sshyp-mfa
|
||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from base64 import b32decode
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from os import environ, listdir, uname
|
||||||
|
from os.path import expanduser, isdir, isfile
|
||||||
|
from sshyp import decrypt, whitelist_verify
|
||||||
|
from subprocess import PIPE, Popen, run
|
||||||
|
from sys import argv, exit as s_exit
|
||||||
|
from time import sleep, strftime, time
|
||||||
|
home = expanduser("~")
|
||||||
|
|
||||||
|
|
||||||
|
def totp(_secret, _algo, _digits, _period): # uses provided information to generate a standard totp key
|
||||||
|
from hmac import new as new_mac
|
||||||
|
from struct import pack, unpack
|
||||||
|
_secret = b32decode(_secret.upper() + '=' * ((8 - len(_secret)) % 8))
|
||||||
|
_counter = pack('>Q', int(time() / _period))
|
||||||
|
_mac = new_mac(_secret, _counter, _algo).digest()
|
||||||
|
_offset = _mac[-1] & 0x0f
|
||||||
|
_binary = unpack('>L', _mac[_offset:_offset + 4])[0] & 0x7fffffff
|
||||||
|
return str(_binary)[-_digits:].zfill(_digits)
|
||||||
|
|
||||||
|
|
||||||
|
def mfa_read_shortcut(): # extracts MFA info from the user-specified sshyp entry
|
||||||
|
if not isfile(f"{directory}{arguments[0]}.gpg"):
|
||||||
|
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not exist\u001b[0m\n")
|
||||||
|
s_exit(1)
|
||||||
|
if quick_unlock_enabled == 'true':
|
||||||
|
_mfa_data = decrypt(directory + arguments[0], _quick_pass=whitelist_verify(port, username_ssh, ip, device_id))
|
||||||
|
else:
|
||||||
|
_mfa_data = decrypt(directory + arguments[0])
|
||||||
|
try:
|
||||||
|
_type = _mfa_data[4].split('otpauth://')[1].split('/')[0]
|
||||||
|
_secret = _mfa_data[4].split('?secret=')[1].split('&issuer=')[0]
|
||||||
|
_algo = _mfa_data[4].split('&algorithm=')[1].split('&digits=')[0]
|
||||||
|
_digits = int(_mfa_data[4].split('&digits=')[1].split('&period=')[0])
|
||||||
|
_period = int(_mfa_data[4].split('&period=')[1])
|
||||||
|
return _type, _secret, _algo, _digits, _period
|
||||||
|
except IndexError:
|
||||||
|
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not contain valid mfa data\u001b[0m\n")
|
||||||
|
s_exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# argument fetcher
|
||||||
|
arguments = argv[1:]
|
||||||
|
if len(arguments) < 1 or not arguments[0].startswith('/'):
|
||||||
|
print("\nsshyp-mfa extension usage: sshyp </entry name> copy -m\n\nrun 'man sshyp-mfa' for more information\n")
|
||||||
|
s_exit(1)
|
||||||
|
|
||||||
|
# user data fetcher
|
||||||
|
sshyp_data = ConfigParser()
|
||||||
|
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
|
||||||
|
quick_unlock_enabled = sshyp_data.get('CLIENT-ONLINE', 'quick_unlock_enabled')
|
||||||
|
username_ssh = sshyp_data.get('SSHYNC', 'user')
|
||||||
|
ip = sshyp_data.get('SSHYNC', 'ip')
|
||||||
|
port = sshyp_data.get('SSHYNC', 'port')
|
||||||
|
directory = sshyp_data.get('SSHYNC', 'local_dir')
|
||||||
|
device_id = listdir(f"{home}/.config/sshyp/devices")[0]
|
||||||
|
|
||||||
|
# main process: runs functions to generate MFA key, then continuously copies up-to-date MFA key to clipboard
|
||||||
|
try:
|
||||||
|
mfa_data, copied = mfa_read_shortcut(), None
|
||||||
|
print('\nmfa key copied to clipboard\n\nuntil this process is closed, your clipboard will be automatically '
|
||||||
|
'updated with the newest mfa key')
|
||||||
|
while True:
|
||||||
|
if str(int(strftime('%S'))/mfa_data[4]).endswith('.0') or copied is None:
|
||||||
|
if copied is None:
|
||||||
|
copied = 1
|
||||||
|
if mfa_data[0] == 'steam':
|
||||||
|
from steam.guard import generate_twofactor_code as steam_totp
|
||||||
|
_mfa_key = steam_totp(b32decode(mfa_data[1]))
|
||||||
|
else:
|
||||||
|
_mfa_key = totp(mfa_data[1], mfa_data[2], mfa_data[3], mfa_data[4])
|
||||||
|
if 'WSL_DISTRO_NAME' in environ: # WSL clipboard detection
|
||||||
|
run(('powershell.exe', '-c', 'Set-Clipboard', _mfa_key))
|
||||||
|
elif 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection
|
||||||
|
run(('wl-copy', _mfa_key))
|
||||||
|
elif uname()[0] == 'Haiku': # Haiku clipboard detection
|
||||||
|
run(('clipboard', '-c', _mfa_key))
|
||||||
|
elif uname()[0] == 'Darwin': # MacOS clipboard detection
|
||||||
|
run('pbcopy', stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
|
||||||
|
elif isdir("/data/data/com.termux"): # Termux (Android) clipboard detection
|
||||||
|
run(('termux-clipboard-set', _mfa_key))
|
||||||
|
else: # X11 clipboard detection
|
||||||
|
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
|
||||||
|
sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\n')
|
||||||
|
s_exit(0)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[sshyp-mfa]
|
||||||
|
supported = true
|
||||||
|
desc = read mfa data from sshyp entries to generate and copy totp keys to the clipboard (optional Steam support)
|
||||||
|
usage = sshyp /<entry name> copy -m
|
||||||
|
py = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.0.1/sshyp-mfa/sshyp-mfa.py
|
||||||
|
ext = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.0.1/sshyp-mfa/sshyp-mfa.ext
|
||||||
Reference in New Issue
Block a user