From 25b72e4c4373792f4c8a140f92f8972748af4767 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 20 Jan 2024 17:34:42 +0100 Subject: [PATCH] Implement totp copy and url launch --- agent/actions/logins.go | 28 ++++++++++++++++++++++------ cmd/logins.go | 2 ++ ipc/messages/notes.go | 3 ++- ui/autofill.py | 14 ++++++++++++++ ui/totp.py | 20 ++++++++++++++++++++ 5 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 ui/totp.py diff --git a/agent/actions/logins.go b/agent/actions/logins.go index a53a662..b2619bf 100644 --- a/agent/actions/logins.go +++ b/agent/actions/logins.go @@ -3,10 +3,7 @@ package actions import ( "fmt" "runtime/debug" - "strings" - "time" - "github.com/pquerna/otp/totp" "github.com/quexten/goldwarden/agent/bitwarden/crypto" "github.com/quexten/goldwarden/agent/config" "github.com/quexten/goldwarden/agent/sockets" @@ -72,9 +69,8 @@ func handleGetLoginCipher(request messages.IPCMessage, cfg *config.Config, vault if !login.Login.Totp.IsNull() { decryptedTotp, err := crypto.DecryptWith(login.Login.Totp, cipherKey) if err == nil { - code, err := totp.GenerateCode(string(strings.ReplaceAll(string(decryptedTotp), " ", "")), time.Now()) if err == nil { - decryptedLogin.TwoFactorCode = code + decryptedLogin.TOTPSeed = string(decryptedTotp) } else { fmt.Println(err) } @@ -101,7 +97,7 @@ func handleGetLoginCipher(request messages.IPCMessage, cfg *config.Config, vault } func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx *sockets.CallingContext) (response messages.IPCMessage, err error) { - if approved, err := pinentry.GetApproval("Approve List Credentials", fmt.Sprintf("%s on %s>%s>%s is trying access all credentials", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved { + if approved, err := pinentry.GetApproval("Access Vault", fmt.Sprintf("%s on %s>%s>%s is trying access ALL CREDENTIALS", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved { response, err = messages.IPCMessageFromPayload(messages.ActionResponse{ Success: false, Message: "not approved", @@ -124,6 +120,8 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va var decryptedName []byte = []byte{} var decryptedUsername []byte = []byte{} var decryptedPassword []byte = []byte{} + var decryptedTotp []byte = []byte{} + var decryptedURL []byte = []byte{} if !login.Name.IsNull() { decryptedName, err = crypto.DecryptWith(login.Name, key) @@ -149,11 +147,29 @@ func handleListLoginsRequest(request messages.IPCMessage, cfg *config.Config, va } } + if !login.Login.Totp.IsNull() { + decryptedTotp, err = crypto.DecryptWith(login.Login.Totp, key) + if err != nil { + actionsLog.Warn("Could not decrypt login:" + err.Error()) + continue + } + } + + if !login.Login.URI.IsNull() { + decryptedURL, err = crypto.DecryptWith(login.Login.URI, key) + if err != nil { + actionsLog.Warn("Could not decrypt login:" + err.Error()) + continue + } + } + decryptedLoginCiphers = append(decryptedLoginCiphers, messages.DecryptedLoginCipher{ Name: string(decryptedName), Username: string(decryptedUsername), UUID: login.ID.String(), Password: string(decryptedPassword), + TOTPSeed: string(decryptedTotp), + URI: string(decryptedURL), }) // prevent deadlock from enclaves diff --git a/cmd/logins.go b/cmd/logins.go index 4251fac..15b5c58 100644 --- a/cmd/logins.go +++ b/cmd/logins.go @@ -79,6 +79,8 @@ var listLoginsCmd = &cobra.Command{ "uuid": stringsx.Clean(login.UUID), "username": stringsx.Clean(login.Username), "password": stringsx.Clean(strings.ReplaceAll(login.Password, "\"", "\\\"")), + "totp": stringsx.Clean(login.TOTPSeed), + "uri": stringsx.Clean(login.URI), } jsonString, err := json.Marshal(data) if err != nil { diff --git a/ipc/messages/notes.go b/ipc/messages/notes.go index 3237e80..22b1f2b 100644 --- a/ipc/messages/notes.go +++ b/ipc/messages/notes.go @@ -28,7 +28,8 @@ type DecryptedLoginCipher struct { UUID string OrgaizationID string Notes string - TwoFactorCode string + TOTPSeed string + URI string } type AddLoginRequest struct { diff --git a/ui/autofill.py b/ui/autofill.py index c1a35a1..3e696d6 100644 --- a/ui/autofill.py +++ b/ui/autofill.py @@ -9,6 +9,7 @@ import clipboard from threading import Thread import sys import os +import totp Notify.init("Goldwarden") class MyApp(Adw.Application): @@ -66,6 +67,8 @@ class MainWindow(Gtk.ApplicationWindow): action_row.password = i["password"] action_row.username = i["username"] action_row.uuid = i["uuid"] + action_row.uri = i["uri"] + action_row.totp = i["totp"] self.history_list.append(action_row) self.starts_with_logins = None self.other_logins = None @@ -109,6 +112,17 @@ class MainWindow(Gtk.ApplicationWindow): return item_uri = environment["vault"] + "#/vault?itemId=" + self.history_list.get_selected_row().uuid Gtk.show_uri(None, item_uri, Gdk.CURRENT_TIME) + elif keyval == 108: + print("launch") + print(self.history_list.get_selected_row().uri) + Gtk.show_uri(None, self.history_list.get_selected_row().uri, Gdk.CURRENT_TIME) + elif keyval == 116: + print("copy totp") + totp_code = totp.totp(self.history_list.get_selected_row().totp) + clipboard.write(totp_code) + notification=Notify.Notification.new("Goldwarden", "Totp Copied", "dialog-information") + notification.set_timeout(5) + notification.show() keycont.connect('key-pressed', handle_keypress, self) self.add_controller(keycont) diff --git a/ui/totp.py b/ui/totp.py new file mode 100644 index 0000000..ff3fa5b --- /dev/null +++ b/ui/totp.py @@ -0,0 +1,20 @@ +# https://github.com/susam/mintotp +#!/usr/bin/env python3 + +import base64 +import hmac +import struct +import sys +import time + +def hotp(key, counter, digits=6, digest='sha1'): + key = base64.b32decode(key.upper() + '=' * ((8 - len(key)) % 8)) + counter = struct.pack('>Q', counter) + mac = hmac.new(key, counter, digest).digest() + offset = mac[-1] & 0x0f + binary = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff + return str(binary)[-digits:].zfill(digits) + + +def totp(key, time_step=30, digits=6, digest='sha1'): + return hotp(key, int(time.time() / time_step), digits, digest) \ No newline at end of file